Unnamed repository; edit this file 'description' to name the repository.
Diffstat (limited to 'crates/ide-assists/src/handlers/unwrap_branch.rs')
-rw-r--r--crates/ide-assists/src/handlers/unwrap_branch.rs995
1 files changed, 995 insertions, 0 deletions
diff --git a/crates/ide-assists/src/handlers/unwrap_branch.rs b/crates/ide-assists/src/handlers/unwrap_branch.rs
new file mode 100644
index 0000000000..a71ba555d0
--- /dev/null
+++ b/crates/ide-assists/src/handlers/unwrap_branch.rs
@@ -0,0 +1,995 @@
+use syntax::{
+ AstNode, SyntaxElement, SyntaxKind, SyntaxNode, T,
+ ast::{
+ self,
+ edit::{AstNodeEdit, IndentLevel},
+ },
+ match_ast,
+ syntax_editor::{Element, Position, SyntaxEditor},
+};
+
+use crate::{AssistContext, AssistId, Assists};
+
+// Assist: unwrap_branch
+//
+// 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_branch(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ let l_curly_token = ctx.find_token_syntax_at_offset(T!['{'])?;
+ let block = l_curly_token.parent_ancestors().nth(1).and_then(ast::BlockExpr::cast)?;
+ let target = block.syntax().text_range();
+ let mut container = block.syntax().clone();
+ let mut replacement = block.clone();
+ let mut prefer_container = None;
+
+ let from_indent = block.indent_level();
+ let into_indent = loop {
+ let parent = container.parent()?;
+ container = match_ast! {
+ match parent {
+ ast::ForExpr(it) => it.syntax().clone(),
+ ast::LoopExpr(it) => it.syntax().clone(),
+ ast::WhileExpr(it) => it.syntax().clone(),
+ ast::MatchArm(it) => it.parent_match().syntax().clone(),
+ ast::LetElse(it) => it.syntax().parent()?,
+ ast::LetStmt(it) => {
+ replacement = wrap_let(&it, replacement);
+ prefer_container = Some(it.syntax().clone());
+ it.syntax().clone()
+ },
+ ast::IfExpr(it) => {
+ prefer_container.get_or_insert_with(|| {
+ if let Some(else_branch) = it.else_branch()
+ && *else_branch.syntax() == container
+ {
+ else_branch.syntax().clone()
+ } else {
+ it.syntax().clone()
+ }
+ });
+ it.syntax().clone()
+ },
+ ast::ExprStmt(it) => it.syntax().clone(),
+ ast::StmtList(it) => break it.indent_level(),
+ _ => return None,
+ }
+ };
+ };
+ let is_branch =
+ !block.is_standalone() || block.syntax().parent().and_then(ast::MatchArm::cast).is_some();
+ let label = if is_branch { "Unwrap branch" } else { "Unwrap block" };
+ let replacement = replacement.stmt_list()?;
+
+ acc.add(AssistId::refactor_rewrite("unwrap_branch"), label, target, |builder| {
+ let editor = builder.make_editor(block.syntax());
+ let replacement = replacement.dedent(from_indent).indent(into_indent);
+ let container = prefer_container.unwrap_or(container);
+
+ editor.replace_with_many(&container, extract_statements(replacement));
+ delete_else_before(container, &editor);
+
+ builder.add_file_edits(ctx.vfs_file_id(), editor);
+ })
+}
+
+fn delete_else_before(container: SyntaxNode, editor: &SyntaxEditor) {
+ let make = editor.make();
+ let Some(else_token) = container
+ .siblings_with_tokens(syntax::Direction::Prev)
+ .skip(1)
+ .map_while(|it| it.into_token())
+ .find(|it| it.kind() == T![else])
+ else {
+ return;
+ };
+ itertools::chain(else_token.prev_token(), else_token.next_token())
+ .filter(|it| it.kind() == SyntaxKind::WHITESPACE)
+ .for_each(|it| editor.delete(it));
+ let indent = IndentLevel::from_node(&container);
+ let newline = make.whitespace(&format!("\n{indent}"));
+ editor.replace(else_token, newline);
+}
+
+fn wrap_let(assign: &ast::LetStmt, replacement: ast::BlockExpr) -> ast::BlockExpr {
+ let try_wrap_assign = || {
+ let initializer = assign.initializer()?.syntax().syntax_element();
+ let (editor, replacement) = SyntaxEditor::with_ast_node(&replacement);
+ let tail_expr = replacement.tail_expr()?;
+ let before =
+ assign.syntax().children_with_tokens().take_while(|it| *it != initializer).collect();
+ let after = assign
+ .syntax()
+ .children_with_tokens()
+ .skip_while(|it| *it != initializer)
+ .skip(1)
+ .collect();
+
+ editor.insert_all(Position::before(tail_expr.syntax()), before);
+ editor.insert_all(Position::after(tail_expr.syntax()), after);
+ ast::BlockExpr::cast(editor.finish().new_root().clone())
+ };
+ try_wrap_assign().unwrap_or(replacement)
+}
+
+fn extract_statements(stmt_list: ast::StmtList) -> Vec<SyntaxElement> {
+ let mut elements = stmt_list
+ .syntax()
+ .children_with_tokens()
+ .filter(|it| !matches!(it.kind(), T!['{'] | T!['}']))
+ .skip_while(|it| it.kind() == SyntaxKind::WHITESPACE)
+ .collect::<Vec<_>>();
+ while elements.pop_if(|it| it.kind() == SyntaxKind::WHITESPACE).is_some() {}
+ elements
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable, check_assist_with_label};
+
+ use super::*;
+
+ #[test]
+ fn unwrap_tail_expr_block() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ $0{
+ 92
+ }
+}
+"#,
+ r#"
+fn main() {
+ 92
+}
+"#,
+ )
+ }
+
+ #[test]
+ fn unwrap_stmt_expr_block() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ $0{
+ 92;
+ }
+ ()
+}
+"#,
+ r#"
+fn main() {
+ 92;
+ ()
+}
+"#,
+ );
+ // Pedantically, we should add an `;` here...
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ $0{
+ 92
+ }
+ ()
+}
+"#,
+ r#"
+fn main() {
+ 92
+ ()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if() {
+ check_assist(
+ unwrap_branch,
+ 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_branch,
+ 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_branch,
+ 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_branch,
+ 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_branch,
+ 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_branch,
+ 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_branch,
+ r#"
+fn main() {
+ bar();$0
+ if true {
+ foo();
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_for() {
+ check_assist(
+ unwrap_branch,
+ 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_branch,
+ 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_branch,
+ 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_branch,
+ 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 simple_let_else() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ let Some(2) = None else {$0
+ return;
+ };
+}
+"#,
+ r#"
+fn main() {
+ return;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ let Some(2) = None else {$0
+ return
+ };
+}
+"#,
+ r#"
+fn main() {
+ return
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_match_arm() {
+ check_assist(
+ unwrap_branch,
+ 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 unwrap_match_arm_in_let() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ let value = 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()?;
+ let value = Some((*id, rel_path));
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_in_while_bad_cursor_position() {
+ check_assist_not_applicable(
+ unwrap_branch,
+ r#"
+fn main() {
+ while true {
+ if true {
+ foo();$0
+
+ // comment
+ bar();
+ } else {
+ println!("bar");
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_single_line() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ {$0 0 }
+}
+"#,
+ r#"
+fn main() {
+ 0
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_nested_block() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ $0{
+ {
+ 3
+ }
+ }
+}
+"#,
+ r#"
+fn main() {
+ {
+ 3
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn nested_single_line() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ {$0 { println!("foo"); } }
+}
+"#,
+ r#"
+fn main() {
+ { println!("foo"); }
+}
+"#,
+ );
+
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ {$0 { 0 } }
+}
+"#,
+ r#"
+fn main() {
+ { 0 }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn simple_if_single_line() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ if true {$0 /* foo */ foo() } else { bar() /* bar */}
+}
+"#,
+ r#"
+fn main() {
+ /* foo */ foo()
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn if_single_statement() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ if true {$0
+ return 3;
+ }
+}
+"#,
+ r#"
+fn main() {
+ return 3;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn multiple_statements() {
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() -> i32 {
+ if 2 > 1 {$0
+ let a = 5;
+ return 3;
+ }
+ 5
+}
+"#,
+ r#"
+fn main() -> i32 {
+ let a = 5;
+ return 3;
+ 5
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_block_in_let_initializers() {
+ // https://github.com/rust-lang/rust-analyzer/issues/13679
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ let x = {$0
+ bar
+ };
+}
+"#,
+ r#"
+fn main() {
+ let x = bar;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() -> i32 {
+ let _ = {$01; 2};
+}
+"#,
+ r#"
+fn main() -> i32 {
+ 1; let _ = 2;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() -> i32 {
+ let mut a = {$01; 2};
+}
+"#,
+ r#"
+fn main() -> i32 {
+ 1; let mut a = 2;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() -> i32 {
+ let mut a = {$0
+ 1;
+ 2;
+ 3
+ };
+}
+"#,
+ r#"
+fn main() -> i32 {
+ 1;
+ 2;
+ let mut a = 3;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_if_in_let_initializers() {
+ // https://github.com/rust-lang/rust-analyzer/issues/13679
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ let a = 1;
+ let x = if a - 1 == 0 {$0
+ foo
+ } else {
+ bar
+ };
+}
+"#,
+ r#"
+fn main() {
+ let a = 1;
+ let x = foo;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_block_with_modifiers() {
+ // https://github.com/rust-lang/rust-analyzer/issues/17964
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ unsafe $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ async move $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ check_assist(
+ unwrap_branch,
+ r#"
+fn main() {
+ try $0{
+ bar;
+ }
+}
+"#,
+ r#"
+fn main() {
+ bar;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn unwrap_block_labels() {
+ check_assist_with_label(
+ unwrap_branch,
+ r#"
+fn main() {
+ $0{
+ bar;
+ }
+}
+"#,
+ "Unwrap block",
+ );
+ check_assist_with_label(
+ unwrap_branch,
+ r#"
+fn main() {
+ let x = $0{
+ bar()
+ };
+}
+"#,
+ "Unwrap block",
+ );
+ check_assist_with_label(
+ unwrap_branch,
+ r#"
+fn main() {
+ let x = if true $0{
+ bar()
+ };
+}
+"#,
+ "Unwrap branch",
+ );
+ check_assist_with_label(
+ unwrap_branch,
+ r#"
+fn main() {
+ let x = match () {
+ () => $0{
+ bar(),
+ }
+ };
+}
+"#,
+ "Unwrap branch",
+ );
+ check_assist_with_label(
+ unwrap_branch,
+ r#"
+fn main() {
+ match () {
+ () => $0{
+ bar(),
+ }
+ }
+}
+"#,
+ "Unwrap branch",
+ );
+ }
+}