Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
189 changes: 189 additions & 0 deletions crates/ide_assists/src/handlers/convert_while_to_loop.rs
Original file line number Diff line number Diff line change
@@ -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();
}
}
"#,
);
}
}
2 changes: 2 additions & 0 deletions crates/ide_assists/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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,
Expand Down
24 changes: 24 additions & 0 deletions crates/ide_assists/src/tests/generated.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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(
Expand Down
5 changes: 5 additions & 0 deletions crates/syntax/src/ast/make.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down