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::>(); 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; }, _ => () }; } "#, ); } }