aboutsummaryrefslogtreecommitdiff
path: root/crates/assists
diff options
context:
space:
mode:
authorJesse Bakker <[email protected]>2021-01-02 00:55:56 +0000
committerJesse Bakker <[email protected]>2021-01-02 15:59:01 +0000
commit31204e3590a59a6c0cbae53d111b699d92fb229f (patch)
tree37c7544efe0b7a870251aa31ae81c18bcf8899e3 /crates/assists
parent56a7bf7ede12f6bec194265ea4a95911c9e469bd (diff)
Add extract-assignment assist
Diffstat (limited to 'crates/assists')
-rw-r--r--crates/assists/src/handlers/extract_assignment.rs238
-rw-r--r--crates/assists/src/lib.rs2
-rw-r--r--crates/assists/src/tests/generated.rs29
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 @@
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 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
58fn 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
72fn 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)]
94mod 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#"
104fn foo() {
105 let mut a = 1;
106
107 if true {
108 <|>a = 2;
109 } else {
110 a = 3;
111 }
112}"#,
113 r#"
114fn 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#"
131fn 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#"
150fn 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#"
162fn 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#"
181fn 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#"
193fn 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#"
212fn 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#"
229fn 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]
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",