aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers
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/src/handlers
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/src/handlers')
-rw-r--r--crates/assists/src/handlers/extract_assignment.rs325
1 files changed, 325 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}