diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs | 719 |
1 files changed, 719 insertions, 0 deletions
diff --git a/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs new file mode 100644 index 000000000..7969a4918 --- /dev/null +++ b/src/tools/rust-analyzer/crates/ide-assists/src/handlers/unwrap_block.rs @@ -0,0 +1,719 @@ +use syntax::{ + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + }, + AstNode, SyntaxKind, TextRange, T, +}; + +use crate::{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_ancestors().nth(1)?)?; + 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::STMT_LIST | 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, + }; + + acc.add(assist_id, assist_label, target, |builder| { + builder.replace(parent.syntax().text_range(), update_expr_string(block.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::<Vec<String>>() + .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() +} +"#, + ); + } + + #[test] + fn if_single_statement() { + check_assist( + unwrap_block, + r#" +fn main() { + if true {$0 + return 3; + } +} +"#, + r#" +fn main() { + return 3; +} +"#, + ); + } + + #[test] + fn multiple_statements() { + check_assist( + unwrap_block, + r#" +fn main() -> i32 { + if 2 > 1 {$0 + let a = 5; + return 3; + } + 5 +} +"#, + r#" +fn main() -> i32 { + let a = 5; + return 3; + 5 +} +"#, + ); + } +} |