use syntax::{ ast::{ self, edit::{AstNodeEdit, IndentLevel}, }, AstNode, SyntaxKind, TextRange, T, }; use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists}; // Assist: unwrap_block // // This assist removes if...else, for, while and loop control statements to just keep the body. // // ``` // fn foo() { // if true {$0 // println!("foo"); // } // } // ``` // -> // ``` // fn foo() { // println!("foo"); // } // ``` pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite); let assist_label = "Unwrap block"; let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?; let mut block = ast::BlockExpr::cast(l_curly_token.parent()?)?; let target = block.syntax().text_range(); let mut parent = block.syntax().parent()?; if ast::MatchArm::can_cast(parent.kind()) { parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))? } if matches!(parent.kind(), SyntaxKind::BLOCK_EXPR | SyntaxKind::EXPR_STMT) { return acc.add(assist_id, assist_label, target, |builder| { builder.replace(block.syntax().text_range(), update_expr_string(block.to_string())); }); } let parent = ast::Expr::cast(parent)?; match parent.clone() { ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (), ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)), ast::Expr::IfExpr(if_expr) => { let then_branch = if_expr.then_branch()?; if then_branch == block { if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) { // For `else if` blocks let ancestor_then_branch = ancestor.then_branch()?; return acc.add(assist_id, assist_label, target, |edit| { let range_to_del_else_if = TextRange::new( ancestor_then_branch.syntax().text_range().end(), l_curly_token.text_range().start(), ); let range_to_del_rest = TextRange::new( then_branch.syntax().text_range().end(), if_expr.syntax().text_range().end(), ); edit.delete(range_to_del_rest); edit.delete(range_to_del_else_if); edit.replace( target, update_expr_string_without_newline(then_branch.to_string()), ); }); } } else { return acc.add(assist_id, assist_label, target, |edit| { let range_to_del = TextRange::new( then_branch.syntax().text_range().end(), l_curly_token.text_range().start(), ); edit.delete(range_to_del); edit.replace(target, update_expr_string_without_newline(block.to_string())); }); } } _ => return None, }; let unwrapped = unwrap_trivial_block(block); acc.add(assist_id, assist_label, target, |builder| { builder.replace(parent.syntax().text_range(), update_expr_string(unwrapped.to_string())); }) } fn update_expr_string(expr_string: String) -> String { update_expr_string_with_pat(expr_string, &[' ', '\n']) } fn update_expr_string_without_newline(expr_string: String) -> String { update_expr_string_with_pat(expr_string, &[' ']) } fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String { // Remove leading whitespace, index [1..] to remove the leading '{', // then continue to remove leading whitespace. let expr_str = expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat); // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}', // then continue to remove trailing whitespace. let expr_str = expr_str.trim_end_matches(whitespace_pat); let expr_str = expr_str[..expr_str.len() - 1].trim_end_matches(whitespace_pat); expr_str .lines() .map(|line| line.replacen(" ", "", 1)) // Delete indentation .collect::>() .join("\n") } #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; use super::*; #[test] fn unwrap_tail_expr_block() { check_assist( unwrap_block, r#" fn main() { $0{ 92 } } "#, r#" fn main() { 92 } "#, ) } #[test] fn unwrap_stmt_expr_block() { check_assist( unwrap_block, r#" fn main() { $0{ 92; } () } "#, r#" fn main() { 92; () } "#, ); // Pedantically, we should add an `;` here... check_assist( unwrap_block, r#" fn main() { $0{ 92 } () } "#, r#" fn main() { 92 () } "#, ); } #[test] fn simple_if() { check_assist( unwrap_block, r#" fn main() { bar(); if true {$0 foo(); // comment bar(); } else { println!("bar"); } } "#, r#" fn main() { bar(); foo(); // comment bar(); } "#, ); } #[test] fn simple_if_else() { check_assist( unwrap_block, r#" fn main() { bar(); if true { foo(); // comment bar(); } else {$0 println!("bar"); } } "#, r#" fn main() { bar(); if true { foo(); // comment bar(); } println!("bar"); } "#, ); } #[test] fn simple_if_else_if() { check_assist( unwrap_block, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false {$0 println!("bar"); } else { println!("foo"); } } "#, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } println!("bar"); } "#, ); } #[test] fn simple_if_else_if_nested() { check_assist( unwrap_block, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } else if true {$0 println!("foo"); } } "#, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } println!("foo"); } "#, ); } #[test] fn simple_if_else_if_nested_else() { check_assist( unwrap_block, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } else if true { println!("foo"); } else {$0 println!("else"); } } "#, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } else if true { println!("foo"); } println!("else"); } "#, ); } #[test] fn simple_if_else_if_nested_middle() { check_assist( unwrap_block, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } else if true {$0 println!("foo"); } else { println!("else"); } } "#, r#" fn main() { // bar(); if true { println!("true"); // comment // bar(); } else if false { println!("bar"); } println!("foo"); } "#, ); } #[test] fn simple_if_bad_cursor_position() { check_assist_not_applicable( unwrap_block, r#" fn main() { bar();$0 if true { foo(); // comment bar(); } else { println!("bar"); } } "#, ); } #[test] fn simple_for() { check_assist( unwrap_block, r#" fn main() { for i in 0..5 {$0 if true { foo(); // comment bar(); } else { println!("bar"); } } } "#, r#" fn main() { if true { foo(); // comment bar(); } else { println!("bar"); } } "#, ); } #[test] fn simple_if_in_for() { check_assist( unwrap_block, r#" fn main() { for i in 0..5 { if true {$0 foo(); // comment bar(); } else { println!("bar"); } } } "#, r#" fn main() { for i in 0..5 { foo(); // comment bar(); } } "#, ); } #[test] fn simple_loop() { check_assist( unwrap_block, r#" fn main() { loop {$0 if true { foo(); // comment bar(); } else { println!("bar"); } } } "#, r#" fn main() { if true { foo(); // comment bar(); } else { println!("bar"); } } "#, ); } #[test] fn simple_while() { check_assist( unwrap_block, r#" fn main() { while true {$0 if true { foo(); // comment bar(); } else { println!("bar"); } } } "#, r#" fn main() { if true { foo(); // comment bar(); } else { println!("bar"); } } "#, ); } #[test] fn unwrap_match_arm() { check_assist( unwrap_block, r#" fn main() { match rel_path { Ok(rel_path) => {$0 let rel_path = RelativePathBuf::from_path(rel_path).ok()?; Some((*id, rel_path)) } Err(_) => None, } } "#, r#" fn main() { let rel_path = RelativePathBuf::from_path(rel_path).ok()?; Some((*id, rel_path)) } "#, ); } #[test] fn simple_if_in_while_bad_cursor_position() { check_assist_not_applicable( unwrap_block, r#" fn main() { while true { if true { foo();$0 // comment bar(); } else { println!("bar"); } } } "#, ); } #[test] fn simple_single_line() { check_assist( unwrap_block, r#" fn main() { {$0 0 } } "#, r#" fn main() { 0 } "#, ); } #[test] fn simple_nested_block() { check_assist( unwrap_block, r#" fn main() { $0{ { 3 } } } "#, r#" fn main() { { 3 } } "#, ); } #[test] fn nested_single_line() { check_assist( unwrap_block, r#" fn main() { {$0 { println!("foo"); } } } "#, r#" fn main() { { println!("foo"); } } "#, ); check_assist( unwrap_block, r#" fn main() { {$0 { 0 } } } "#, r#" fn main() { { 0 } } "#, ); } #[test] fn simple_if_single_line() { check_assist( unwrap_block, r#" fn main() { if true {$0 /* foo */ foo() } else { bar() /* bar */} } "#, r#" fn main() { /* foo */ foo() } "#, ); } }