From 11a56f886bb07a5207f1236536247271cbbe7a12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Andrzej=20G=C5=82uszak?= Date: Thu, 16 Sep 2021 15:36:44 +0200 Subject: [PATCH] assists: turn while into loop --- .../src/handlers/convert_while_to_loop.rs | 189 ++++++++++++++++++ crates/ide_assists/src/lib.rs | 2 + crates/ide_assists/src/tests/generated.rs | 24 +++ crates/syntax/src/ast/make.rs | 5 + 4 files changed, 220 insertions(+) create mode 100644 crates/ide_assists/src/handlers/convert_while_to_loop.rs diff --git a/crates/ide_assists/src/handlers/convert_while_to_loop.rs b/crates/ide_assists/src/handlers/convert_while_to_loop.rs new file mode 100644 index 00000000000..cbddc106ffb --- /dev/null +++ b/crates/ide_assists/src/handlers/convert_while_to_loop.rs @@ -0,0 +1,189 @@ +use std::iter::once; + +use syntax::{ + ast::{ + self, + edit::{AstNodeEdit, IndentLevel}, + make, LoopBodyOwner, + }, + AstNode, T, +}; + +use crate::{ + assist_context::{AssistContext, Assists}, + utils::invert_boolean_expression, + AssistId, AssistKind, +}; + +// Assist: convert_while_to_loop +// +// Replace a while with a loop. +// +// ``` +// fn main() { +// $0while cond { +// foo(); +// } +// } +// ``` +// -> +// ``` +// fn main() { +// loop { +// if !cond { +// break; +// } +// foo(); +// } +// } +// ``` +pub(crate) fn convert_while_to_loop(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let while_kw = ctx.find_token_syntax_at_offset(T![while])?; + let while_expr: ast::WhileExpr = while_kw.parent().and_then(ast::WhileExpr::cast)?; + let while_body = while_expr.loop_body()?; + let cond = while_expr.condition()?; + + // Don't handle while let + if let Some(_) = cond.pat() { + return None; + }; + + let cond_expr = cond.expr()?; + + let target = while_expr.syntax().text_range(); + acc.add( + AssistId("convert_while_to_loop", AssistKind::RefactorRewrite), + "Convert while to loop", + target, + |edit| { + let while_indent_level = IndentLevel::from_node(while_expr.syntax()); + + let replacement = { + let if_expr = { + let cond = invert_boolean_expression(cond_expr); + let then_branch = make::block_expr( + once(make::expr_stmt(make::expr_break(None)).into()), + None, + ); + + make::expr_if(make::condition(cond, None), then_branch, None) + }; + + let if_expr = if_expr.indent(while_indent_level); + let stmts = once(make::expr_stmt(if_expr).into()).chain(while_body.statements()); + + let block_expr = make::block_expr(stmts, while_body.tail_expr()); + + let block_expr = block_expr.indent(while_indent_level); + + make::expr_loop(block_expr) + }; + + edit.replace(target, replacement.syntax().text()) + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn convert_inside_fn() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + while$0 cond { + foo(); + } +} +"#, + r#" +fn main() { + loop { + if !cond { + break; + } + foo(); + } +} +"#, + ); + } + + #[test] + fn convert_busy_wait() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + while$0 cond() {} +} +"#, + r#" +fn main() { + loop { + if !cond() { + break; + } + } +} +"#, + ); + } + + #[test] + fn convert_trailing_expr() { + check_assist( + convert_while_to_loop, + r#" +fn main() { + while$0 cond() { + bar() + } +} +"#, + r#" +fn main() { + loop { + if !cond() { + break; + } + bar() + } +} +"#, + ); + } + + #[test] + fn ignore_while_let() { + check_assist_not_applicable( + convert_while_to_loop, + r#" +fn main() { + while$0 let Some(_) = foo() { + bar(); + } +} +"#, + ); + } + + #[test] + fn ignore_cursor_in_body() { + check_assist_not_applicable( + convert_while_to_loop, + r#" +fn main() { + while cond {$0 + bar(); + } +} +"#, + ); + } +} diff --git a/crates/ide_assists/src/lib.rs b/crates/ide_assists/src/lib.rs index b4fb5c190f7..2fb8cb3492d 100644 --- a/crates/ide_assists/src/lib.rs +++ b/crates/ide_assists/src/lib.rs @@ -118,6 +118,7 @@ mod handlers { mod convert_iter_for_each_to_for; mod convert_tuple_struct_to_named_struct; mod convert_to_guarded_return; + mod convert_while_to_loop; mod destructure_tuple_binding; mod expand_glob_import; mod extract_function; @@ -191,6 +192,7 @@ mod handlers { convert_iter_for_each_to_for::convert_iter_for_each_to_for, convert_to_guarded_return::convert_to_guarded_return, convert_tuple_struct_to_named_struct::convert_tuple_struct_to_named_struct, + convert_while_to_loop::convert_while_to_loop, destructure_tuple_binding::destructure_tuple_binding, expand_glob_import::expand_glob_import, extract_struct_from_enum_variant::extract_struct_from_enum_variant, diff --git a/crates/ide_assists/src/tests/generated.rs b/crates/ide_assists/src/tests/generated.rs index 46dd409409b..01db0162e3a 100644 --- a/crates/ide_assists/src/tests/generated.rs +++ b/crates/ide_assists/src/tests/generated.rs @@ -367,6 +367,30 @@ impl Point { ) } +#[test] +fn doctest_convert_while_to_loop() { + check_doc_test( + "convert_while_to_loop", + r#####" +fn main() { + $0while cond { + foo(); + } +} +"#####, + r#####" +fn main() { + loop { + if !cond { + break; + } + foo(); + } +} +"#####, + ) +} + #[test] fn doctest_destructure_tuple_binding() { check_doc_test( diff --git a/crates/syntax/src/ast/make.rs b/crates/syntax/src/ast/make.rs index 93eca19c309..819d53982f7 100644 --- a/crates/syntax/src/ast/make.rs +++ b/crates/syntax/src/ast/make.rs @@ -316,6 +316,11 @@ pub fn expr_if( pub fn expr_for_loop(pat: ast::Pat, expr: ast::Expr, block: ast::BlockExpr) -> ast::Expr { expr_from_text(&format!("for {} in {} {}", pat, expr, block)) } + +pub fn expr_loop(block: ast::BlockExpr) -> ast::Expr { + expr_from_text(&format!("loop {}", block)) +} + pub fn expr_prefix(op: SyntaxKind, expr: ast::Expr) -> ast::Expr { let token = token(op); expr_from_text(&format!("{}{}", token, expr))