Add extract-assignment assist

This commit is contained in:
Jesse Bakker 2021-01-02 01:55:56 +01:00
parent 56a7bf7ede
commit 31204e3590
3 changed files with 269 additions and 0 deletions

View file

@ -0,0 +1,238 @@
use hir::AsName;
use syntax::{
ast::{self, edit::AstNodeEdit, make},
AstNode,
};
use test_utils::mark;
use crate::{
assist_context::{AssistContext, Assists},
AssistId, AssistKind,
};
// Assist: extract_assignment
//
// Extracts variable assigment to outside an if or match statement.
//
// ```
// fn main() {
// let mut foo = 6;
//
// if true {
// <|>foo = 5;
// } else {
// foo = 4;
// }
// }
// ```
// ->
// ```
// fn main() {
// let mut foo = 6;
//
// foo = if true {
// 5
// } else {
// 4
// };
// }
// ```
pub(crate) fn extract_assigment(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
let name = ctx.find_node_at_offset::<ast::NameRef>()?.as_name();
let if_statement = ctx.find_node_at_offset::<ast::IfExpr>()?;
let new_stmt = exprify_if(&if_statement, &name)?.indent(if_statement.indent_level());
let expr_stmt = make::expr_stmt(new_stmt);
acc.add(
AssistId("extract_assignment", AssistKind::RefactorExtract),
"Extract assignment",
if_statement.syntax().text_range(),
move |edit| {
edit.replace(if_statement.syntax().text_range(), format!("{} = {};", name, expr_stmt));
},
)
}
fn exprify_if(statement: &ast::IfExpr, name: &hir::Name) -> Option<ast::Expr> {
let then_branch = exprify_block(&statement.then_branch()?, name)?;
let else_branch = match statement.else_branch()? {
ast::ElseBranch::Block(block) => ast::ElseBranch::Block(exprify_block(&block, name)?),
ast::ElseBranch::IfExpr(expr) => {
mark::hit!(test_extract_assigment_chained_if);
ast::ElseBranch::IfExpr(ast::IfExpr::cast(
exprify_if(&expr, name)?.syntax().to_owned(),
)?)
}
};
Some(make::expr_if(statement.condition()?, then_branch, Some(else_branch)))
}
fn exprify_block(block: &ast::BlockExpr, name: &hir::Name) -> Option<ast::BlockExpr> {
if block.expr().is_some() {
return None;
}
let mut stmts: Vec<_> = block.statements().collect();
let stmt = stmts.pop()?;
if let ast::Stmt::ExprStmt(stmt) = stmt {
if let ast::Expr::BinExpr(expr) = stmt.expr()? {
if expr.op_kind()? == ast::BinOp::Assignment
&& &expr.lhs()?.name_ref()?.as_name() == name
{
// The last statement in the block is an assignment to the name we want
return Some(make::block_expr(stmts, Some(expr.rhs()?)));
}
}
}
None
}
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::{check_assist, check_assist_not_applicable};
#[test]
fn test_extract_assignment() {
check_assist(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
if true {
<|>a = 2;
} else {
a = 3;
}
}"#,
r#"
fn foo() {
let mut a = 1;
a = if true {
2
} else {
3
};
}"#,
);
}
#[test]
fn test_extract_assignment_not_last_not_applicable() {
check_assist_not_applicable(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
if true {
<|>a = 2;
b = a;
} else {
a = 3;
}
}"#,
)
}
#[test]
fn test_extract_assignment_chained_if() {
mark::check!(test_extract_assigment_chained_if);
check_assist(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
if true {
<|>a = 2;
} else if false {
a = 3;
} else {
a = 4;
}
}"#,
r#"
fn foo() {
let mut a = 1;
a = if true {
2
} else if false {
3
} else {
4
};
}"#,
);
}
#[test]
fn test_extract_assigment_retains_stmts() {
check_assist(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
if true {
let b = 2;
<|>a = 2;
} else {
let b = 3;
a = 3;
}
}"#,
r#"
fn foo() {
let mut a = 1;
a = if true {
let b = 2;
2
} else {
let b = 3;
3
};
}"#,
)
}
#[test]
fn extract_assignment_let_stmt_not_applicable() {
check_assist_not_applicable(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
let b = if true {
<|>a = 2
} else {
a = 3
};
}"#,
)
}
#[test]
fn extract_assignment_missing_assigment_not_applicable() {
check_assist_not_applicable(
extract_assigment,
r#"
fn foo() {
let mut a = 1;
if true {
<|>a = 2;
} else {}
}"#,
)
}
}

View file

@ -116,6 +116,7 @@ mod handlers {
mod convert_integer_literal;
mod early_return;
mod expand_glob_import;
mod extract_assignment;
mod extract_module_to_file;
mod extract_struct_from_enum_variant;
mod extract_variable;
@ -167,6 +168,7 @@ mod handlers {
convert_integer_literal::convert_integer_literal,
early_return::convert_to_guarded_return,
expand_glob_import::expand_glob_import,
extract_assignment::extract_assigment,
extract_module_to_file::extract_module_to_file,
extract_struct_from_enum_variant::extract_struct_from_enum_variant,
extract_variable::extract_variable,

View file

@ -237,6 +237,35 @@ fn qux(bar: Bar, baz: Baz) {}
)
}
#[test]
fn doctest_extract_assignment() {
check_doc_test(
"extract_assignment",
r#####"
fn main() {
let mut foo = 6;
if true {
<|>foo = 5;
} else {
foo = 4;
}
}
"#####,
r#####"
fn main() {
let mut foo = 6;
foo = if true {
5
} else {
4
};
}
"#####,
)
}
#[test]
fn doctest_extract_module_to_file() {
check_doc_test(