1 use std::ops::RangeInclusive;
3 use hir::db::HirDatabase;
5 algo::replace_children,
6 ast::{self, edit::IndentLevel, make},
8 SyntaxKind::{FN_DEF, LOOP_EXPR, L_CURLY, R_CURLY, WHILE_EXPR, WHITESPACE},
12 assist_ctx::{Assist, AssistCtx},
16 // Assist: convert_to_guarded_return
18 // Replace a large conditional with a guarded return.
38 pub(crate) fn convert_to_guarded_return(mut ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
39 let if_expr: ast::IfExpr = ctx.node_at_offset()?;
40 let expr = if_expr.condition()?.expr()?;
41 let then_block = if_expr.then_branch()?.block()?;
42 if if_expr.else_branch().is_some() {
46 let parent_block = if_expr.syntax().parent()?.ancestors().find_map(ast::Block::cast)?;
48 if parent_block.expr()? != if_expr.clone().into() {
52 // check for early return and continue
53 let first_in_then_block = then_block.syntax().first_child()?.clone();
54 if ast::ReturnExpr::can_cast(first_in_then_block.kind())
55 || ast::ContinueExpr::can_cast(first_in_then_block.kind())
56 || first_in_then_block
58 .any(|x| ast::ReturnExpr::can_cast(x.kind()) || ast::ContinueExpr::can_cast(x.kind()))
63 let parent_container = parent_block.syntax().parent()?.parent()?;
65 let early_expression = match parent_container.kind() {
66 WHILE_EXPR | LOOP_EXPR => Some("continue;"),
67 FN_DEF => Some("return;"),
71 if then_block.syntax().first_child_or_token().map(|t| t.kind() == L_CURLY).is_none() {
75 then_block.syntax().last_child_or_token().filter(|t| t.kind() == R_CURLY)?;
76 let cursor_position = ctx.frange.range.start();
78 ctx.add_action(AssistId("convert_to_guarded_return"), "convert to guarded return", |edit| {
79 let if_indent_level = IndentLevel::from_node(&if_expr.syntax());
81 if_indent_level.increase_indent(make::if_expression(&expr, early_expression));
82 let then_block_items = IndentLevel::from(1).decrease_indent(then_block.clone());
83 let end_of_then = then_block_items.syntax().last_child_or_token().unwrap();
85 if end_of_then.prev_sibling_or_token().map(|n| n.kind()) == Some(WHITESPACE) {
86 end_of_then.prev_sibling_or_token().unwrap()
90 let mut new_if_and_then_statements = new_if_expr.syntax().children_with_tokens().chain(
93 .children_with_tokens()
95 .take_while(|i| *i != end_of_then),
97 let new_block = replace_children(
98 &parent_block.syntax(),
100 if_expr.clone().syntax().clone().into(),
101 if_expr.syntax().clone().into(),
103 &mut new_if_and_then_statements,
105 edit.target(if_expr.syntax().text_range());
106 edit.replace_ast(parent_block, ast::Block::cast(new_block).unwrap());
107 edit.set_cursor(cursor_position);
115 use crate::helpers::{check_assist, check_assist_not_applicable};
118 fn convert_inside_fn() {
120 convert_to_guarded_return,
148 fn convert_inside_while() {
150 convert_to_guarded_return,
176 fn convert_inside_loop() {
178 convert_to_guarded_return,
204 fn ignore_already_converted_if() {
205 check_assist_not_applicable(
206 convert_to_guarded_return,
218 fn ignore_already_converted_loop() {
219 check_assist_not_applicable(
220 convert_to_guarded_return,
235 check_assist_not_applicable(
236 convert_to_guarded_return,
248 fn ignore_else_branch() {
249 check_assist_not_applicable(
250 convert_to_guarded_return,
264 fn ignore_statements_aftert_if() {
265 check_assist_not_applicable(
266 convert_to_guarded_return,
279 fn ignore_statements_inside_if() {
280 check_assist_not_applicable(
281 convert_to_guarded_return,