Skip to content

Commit 3e2159a

Browse files
committed
Add extract-assignment assist
1 parent 56a7bf7 commit 3e2159a

File tree

3 files changed

+168
-0
lines changed

3 files changed

+168
-0
lines changed
Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
use syntax::{
2+
ast::{edit::AstNodeEdit, make, BinOp, BlockExpr, ElseBranch, Expr, IfExpr, NameRef, Stmt},
3+
AstNode,
4+
};
5+
6+
use crate::{
7+
assist_context::{AssistContext, Assists},
8+
AssistId, AssistKind,
9+
};
10+
11+
// Assist: extract_assignment
12+
//
13+
// Extracts variable assigment to outside an if or match statement.
14+
//
15+
// ```
16+
// fn main() {
17+
// let mut foo = 6;
18+
//
19+
// if true {
20+
// <|>foo = 5;
21+
// } else {
22+
// foo = 4;
23+
// }
24+
// }
25+
// ```
26+
// ->
27+
// ```
28+
// fn main() {
29+
// let mut foo = 6;
30+
//
31+
// foo = if true {
32+
// 5
33+
// } else {
34+
// 4
35+
// }
36+
// }
37+
// ```
38+
pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
39+
let name = ctx.find_node_at_offset::<NameRef>()?.to_string();
40+
41+
let if_statement = ctx.find_node_at_offset::<IfExpr>()?;
42+
43+
let new_stmt =
44+
exprify_if(&if_statement, &name.to_string())?.indent(if_statement.indent_level());
45+
let expr_stmt = make::expr_stmt(new_stmt);
46+
47+
acc.add(
48+
AssistId("extract_assignment", AssistKind::RefactorExtract),
49+
"Extract assignment",
50+
if_statement.syntax().text_range(),
51+
move |edit| {
52+
edit.replace(if_statement.syntax().text_range(), format!("{} = {}", name, expr_stmt));
53+
},
54+
)
55+
}
56+
57+
fn exprify_if(statement: &IfExpr, name: &str) -> Option<Expr> {
58+
let then_branch = exprify_block(statement.then_branch()?, name)?;
59+
let else_branch = match statement.else_branch()? {
60+
ElseBranch::Block(block) => ElseBranch::Block(exprify_block(block, name)?),
61+
ElseBranch::IfExpr(expr) => {
62+
ElseBranch::IfExpr(IfExpr::cast(exprify_if(&expr, name)?.syntax().to_owned())?)
63+
}
64+
};
65+
Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
66+
}
67+
68+
fn exprify_block(block: BlockExpr, name: &str) -> Option<BlockExpr> {
69+
if block.expr().is_some() {
70+
return None;
71+
}
72+
73+
let mut stmts: Vec<_> = block.statements().collect();
74+
let stmt = stmts.pop()?;
75+
76+
if let Stmt::ExprStmt(stmt) = stmt {
77+
if let Expr::BinExpr(expr) = stmt.expr()? {
78+
if expr.op_kind()? == BinOp::Assignment && expr.lhs()?.name_ref()?.to_string() == name {
79+
// The last statement in the block is an assignment to the name we want
80+
return Some(make::block_expr(stmts, Some(expr.rhs()?)));
81+
}
82+
}
83+
}
84+
None
85+
}
86+
87+
#[cfg(test)]
88+
mod tests {
89+
use super::*;
90+
91+
use crate::tests::{check_assist, check_assist_not_applicable};
92+
93+
#[test]
94+
fn test_extract_assignment() {
95+
check_assist(
96+
extract_assigment,
97+
r#"
98+
fn foo() {
99+
let mut a = 1;
100+
101+
if true {
102+
<|>a = 2;
103+
} else {
104+
a = 3;
105+
}
106+
}"#,
107+
r#"
108+
fn foo() {
109+
let mut a = 1;
110+
111+
a = if true {
112+
2
113+
} else {
114+
3
115+
}
116+
}"#,
117+
);
118+
}
119+
120+
#[test]
121+
fn test_extract_assignment_not_last_not_applicable() {
122+
check_assist_not_applicable(
123+
extract_assigment,
124+
r#"
125+
fn foo() {
126+
let mut a = 1;
127+
128+
if true {
129+
<|>a = 2;
130+
b = a;
131+
} else {
132+
a = 3;
133+
}
134+
}"#,
135+
)
136+
}
137+
}

crates/assists/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,7 @@ mod handlers {
116116
mod convert_integer_literal;
117117
mod early_return;
118118
mod expand_glob_import;
119+
mod extract_assignment;
119120
mod extract_module_to_file;
120121
mod extract_struct_from_enum_variant;
121122
mod extract_variable;
@@ -167,6 +168,7 @@ mod handlers {
167168
convert_integer_literal::convert_integer_literal,
168169
early_return::convert_to_guarded_return,
169170
expand_glob_import::expand_glob_import,
171+
extract_assignment::extract_assigment,
170172
extract_module_to_file::extract_module_to_file,
171173
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
172174
extract_variable::extract_variable,

crates/assists/src/tests/generated.rs

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -237,6 +237,35 @@ fn qux(bar: Bar, baz: Baz) {}
237237
)
238238
}
239239

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+
240269
#[test]
241270
fn doctest_extract_module_to_file() {
242271
check_doc_test(

0 commit comments

Comments
 (0)