blob: 99ee50fa5848f1975e5c7e70ecff7cc9fe6aeaa9 [file] [log] [blame]
use either::Either;
use syntax::{
AstNode, T,
ast::{self, edit::AstNodeEdit, syntax_factory::SyntaxFactory},
match_ast,
};
use crate::{AssistContext, AssistId, Assists};
// Assist: add_braces
//
// Adds braces to closure bodies, match arm expressions and assignment bodies.
//
// ```
// fn foo(n: i32) -> i32 {
// match n {
// 1 =>$0 n + 1,
// _ => 0
// }
// }
// ```
// ->
// ```
// fn foo(n: i32) -> i32 {
// match n {
// 1 => {
// n + 1
// },
// _ => 0
// }
// }
// ```
// ---
// ```
// fn foo(n: i32) -> i32 {
// let x =$0 n + 2;
// }
// ```
// ->
// ```
// fn foo(n: i32) -> i32 {
// let x = {
// n + 2
// };
// }
// ```
pub(crate) fn add_braces(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
let (expr_type, expr) = get_replacement_node(ctx)?;
acc.add(
AssistId::refactor_rewrite("add_braces"),
match expr_type {
ParentType::ClosureExpr => "Add braces to this closure body",
ParentType::MatchArmExpr => "Add braces to this match arm expression",
ParentType::Assignment => "Add braces to this assignment expression",
},
expr.syntax().text_range(),
|builder| {
let make = SyntaxFactory::with_mappings();
let mut editor = builder.make_editor(expr.syntax());
let new_expr = expr.reset_indent().indent(1.into());
let block_expr = make.block_expr(None, Some(new_expr));
editor.replace(expr.syntax(), block_expr.indent(expr.indent_level()).syntax());
editor.add_mappings(make.finish_with_mappings());
builder.add_file_edits(ctx.vfs_file_id(), editor);
},
)
}
enum ParentType {
MatchArmExpr,
ClosureExpr,
Assignment,
}
fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Expr)> {
let node = ctx.find_node_at_offset::<Either<ast::MatchArm, ast::ClosureExpr>>();
let (parent_type, body) = if let Some(eq_token) = ctx.find_token_syntax_at_offset(T![=]) {
let parent = eq_token.parent()?;
let body = match_ast! {
match parent {
ast::LetStmt(it) => it.initializer()?,
ast::LetExpr(it) => it.expr()?,
ast::Static(it) => it.body()?,
ast::Const(it) => it.body()?,
_ => return None,
}
};
(ParentType::Assignment, body)
} else if let Some(Either::Left(match_arm)) = &node {
let match_arm_expr = match_arm.expr()?;
(ParentType::MatchArmExpr, match_arm_expr)
} else if let Some(Either::Right(closure_expr)) = &node {
let body = closure_expr.body()?;
(ParentType::ClosureExpr, body)
} else {
return None;
};
if matches!(body, ast::Expr::BlockExpr(_)) {
return None;
}
Some((parent_type, body))
}
#[cfg(test)]
mod tests {
use crate::tests::{check_assist, check_assist_not_applicable};
use super::*;
#[test]
fn suggest_add_braces_for_closure() {
check_assist(
add_braces,
r#"
fn foo() {
t(|n|$0 n + 100);
}
"#,
r#"
fn foo() {
t(|n| {
n + 100
});
}
"#,
);
}
#[test]
fn suggest_add_braces_for_closure_in_match() {
check_assist(
add_braces,
r#"
fn foo() {
match () {
() => {
t(|n|$0 n + 100);
}
}
}
"#,
r#"
fn foo() {
match () {
() => {
t(|n| {
n + 100
});
}
}
}
"#,
);
}
#[test]
fn suggest_add_braces_for_assignment() {
check_assist(
add_braces,
r#"
fn foo() {
let x =$0 n + 100;
}
"#,
r#"
fn foo() {
let x = {
n + 100
};
}
"#,
);
}
#[test]
fn no_assist_for_closures_with_braces() {
check_assist_not_applicable(
add_braces,
r#"
fn foo() {
t(|n|$0 { n + 100 });
}
"#,
);
}
#[test]
fn suggest_add_braces_for_match() {
check_assist(
add_braces,
r#"
fn foo() {
match n {
Some(n) $0=> 29,
_ => ()
};
}
"#,
r#"
fn foo() {
match n {
Some(n) => {
29
},
_ => ()
};
}
"#,
);
}
#[test]
fn multiple_indent() {
check_assist(
add_braces,
r#"
fn foo() {
{
match n {
Some(n) $0=> foo(
29,
30,
),
_ => ()
};
}
}
"#,
r#"
fn foo() {
{
match n {
Some(n) => {
foo(
29,
30,
)
},
_ => ()
};
}
}
"#,
);
}
#[test]
fn no_assist_for_match_with_braces() {
check_assist_not_applicable(
add_braces,
r#"
fn foo() {
match n {
Some(n) $0=> { return 29; },
_ => ()
};
}
"#,
);
}
}