4 edit::{AstNodeEdit, IndentLevel},
6 AstNode, SyntaxKind, TextRange, T,
9 use crate::{utils::unwrap_trivial_block, AssistContext, AssistId, AssistKind, Assists};
11 // Assist: unwrap_block
13 // This assist removes if...else, for, while and loop control statements to just keep the body.
28 pub(crate) fn unwrap_block(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
29 let assist_id = AssistId("unwrap_block", AssistKind::RefactorRewrite);
30 let assist_label = "Unwrap block";
32 let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
33 let mut block = ast::BlockExpr::cast(l_curly_token.ancestors().nth(1)?)?;
34 let target = block.syntax().text_range();
35 let mut parent = block.syntax().parent()?;
36 if ast::MatchArm::can_cast(parent.kind()) {
37 parent = parent.ancestors().find(|it| ast::MatchExpr::can_cast(it.kind()))?
40 if matches!(parent.kind(), SyntaxKind::STMT_LIST | SyntaxKind::EXPR_STMT) {
41 return acc.add(assist_id, assist_label, target, |builder| {
42 builder.replace(block.syntax().text_range(), update_expr_string(block.to_string()));
46 let parent = ast::Expr::cast(parent)?;
48 match parent.clone() {
49 ast::Expr::ForExpr(_) | ast::Expr::WhileExpr(_) | ast::Expr::LoopExpr(_) => (),
50 ast::Expr::MatchExpr(_) => block = block.dedent(IndentLevel(1)),
51 ast::Expr::IfExpr(if_expr) => {
52 let then_branch = if_expr.then_branch()?;
53 if then_branch == block {
54 if let Some(ancestor) = if_expr.syntax().parent().and_then(ast::IfExpr::cast) {
55 // For `else if` blocks
56 let ancestor_then_branch = ancestor.then_branch()?;
58 return acc.add(assist_id, assist_label, target, |edit| {
59 let range_to_del_else_if = TextRange::new(
60 ancestor_then_branch.syntax().text_range().end(),
61 l_curly_token.text_range().start(),
63 let range_to_del_rest = TextRange::new(
64 then_branch.syntax().text_range().end(),
65 if_expr.syntax().text_range().end(),
68 edit.delete(range_to_del_rest);
69 edit.delete(range_to_del_else_if);
72 update_expr_string_without_newline(then_branch.to_string()),
77 return acc.add(assist_id, assist_label, target, |edit| {
78 let range_to_del = TextRange::new(
79 then_branch.syntax().text_range().end(),
80 l_curly_token.text_range().start(),
83 edit.delete(range_to_del);
84 edit.replace(target, update_expr_string_without_newline(block.to_string()));
91 let unwrapped = unwrap_trivial_block(block);
92 acc.add(assist_id, assist_label, target, |builder| {
93 builder.replace(parent.syntax().text_range(), update_expr_string(unwrapped.to_string()));
97 fn update_expr_string(expr_string: String) -> String {
98 update_expr_string_with_pat(expr_string, &[' ', '\n'])
101 fn update_expr_string_without_newline(expr_string: String) -> String {
102 update_expr_string_with_pat(expr_string, &[' '])
105 fn update_expr_string_with_pat(expr_str: String, whitespace_pat: &[char]) -> String {
106 // Remove leading whitespace, index [1..] to remove the leading '{',
107 // then continue to remove leading whitespace.
109 expr_str.trim_start_matches(whitespace_pat)[1..].trim_start_matches(whitespace_pat);
111 // Remove trailing whitespace, index [..expr_str.len() - 1] to remove the trailing '}',
112 // then continue to remove trailing whitespace.
113 let expr_str = expr_str.trim_end_matches(whitespace_pat);
114 let expr_str = expr_str[..expr_str.len() - 1].trim_end_matches(whitespace_pat);
118 .map(|line| line.replacen(" ", "", 1)) // Delete indentation
119 .collect::<Vec<String>>()
125 use crate::tests::{check_assist, check_assist_not_applicable};
130 fn unwrap_tail_expr_block() {
149 fn unwrap_stmt_expr_block() {
167 // Pedantically, we should add an `;` here...
217 fn simple_if_else() {
249 fn simple_if_else_if() {
283 fn simple_if_else_if_nested() {
319 fn simple_if_else_if_nested_else() {
359 fn simple_if_else_if_nested_middle() {
397 fn simple_if_bad_cursor_position() {
398 check_assist_not_applicable(
450 fn simple_if_in_for() {
547 fn unwrap_match_arm() {
554 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
555 Some((*id, rel_path))
563 let rel_path = RelativePathBuf::from_path(rel_path).ok()?;
564 Some((*id, rel_path))
571 fn simple_if_in_while_bad_cursor_position() {
572 check_assist_not_applicable(
592 fn simple_single_line() {
609 fn simple_nested_block() {
632 fn nested_single_line() {
637 {$0 { println!("foo"); } }
663 fn simple_if_single_line() {
668 if true {$0 /* foo */ foo() } else { bar() /* bar */}