ide_diagnostics/handlers/
remove_unnecessary_else.rsuse hir::{db::ExpandDatabase, diagnostics::RemoveUnnecessaryElse, HirFileIdExt};
use ide_db::text_edit::TextEdit;
use ide_db::{assists::Assist, source_change::SourceChange};
use itertools::Itertools;
use syntax::{
ast::{
self,
edit::{AstNodeEdit, IndentLevel},
},
AstNode, SyntaxToken, TextRange,
};
use crate::{
adjusted_display_range, fix, Diagnostic, DiagnosticCode, DiagnosticsContext, Severity,
};
pub(crate) fn remove_unnecessary_else(
ctx: &DiagnosticsContext<'_>,
d: &RemoveUnnecessaryElse,
) -> Option<Diagnostic> {
if d.if_expr.file_id.macro_file().is_some() {
return None;
}
let display_range = adjusted_display_range(ctx, d.if_expr, &|if_expr| {
if_expr.else_token().as_ref().map(SyntaxToken::text_range)
});
Some(
Diagnostic::new(
DiagnosticCode::Ra("remove-unnecessary-else", Severity::WeakWarning),
"remove unnecessary else block",
display_range,
)
.experimental()
.with_fixes(fixes(ctx, d)),
)
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveUnnecessaryElse) -> Option<Vec<Assist>> {
let root = ctx.sema.db.parse_or_expand(d.if_expr.file_id);
let if_expr = d.if_expr.value.to_node(&root);
let if_expr = ctx.sema.original_ast_node(if_expr)?;
let mut indent = IndentLevel::from_node(if_expr.syntax());
let has_parent_if_expr = if_expr.syntax().parent().and_then(ast::IfExpr::cast).is_some();
if has_parent_if_expr {
indent = indent + 1;
}
let else_replacement = match if_expr.else_branch()? {
ast::ElseBranch::Block(block) => block
.statements()
.map(|stmt| format!("\n{indent}{stmt}"))
.chain(block.tail_expr().map(|tail| format!("\n{indent}{tail}")))
.join(""),
ast::ElseBranch::IfExpr(mut nested_if_expr) => {
if has_parent_if_expr {
nested_if_expr = nested_if_expr.indent(IndentLevel(1))
}
format!("\n{indent}{nested_if_expr}")
}
};
let (replacement, range) = if has_parent_if_expr {
let base_indent = IndentLevel::from_node(if_expr.syntax());
let then_indent = base_indent + 1;
let then_child_indent = then_indent + 1;
let condition = if_expr.condition()?;
let then_stmts = if_expr
.then_branch()?
.statements()
.map(|stmt| format!("\n{then_child_indent}{stmt}"))
.join("");
let then_replacement =
format!("\n{then_indent}if {condition} {{{then_stmts}\n{then_indent}}}",);
let replacement = format!("{{{then_replacement}{else_replacement}\n{base_indent}}}");
(replacement, if_expr.syntax().text_range())
} else {
(
else_replacement,
TextRange::new(
if_expr.then_branch()?.syntax().text_range().end(),
if_expr.syntax().text_range().end(),
),
)
};
let edit = TextEdit::replace(range, replacement);
let source_change =
SourceChange::from_text_edit(d.if_expr.file_id.original_file(ctx.sema.db), edit);
Some(vec![fix(
"remove_unnecessary_else",
"Remove unnecessary else block",
source_change,
range,
)])
}
#[cfg(test)]
mod tests {
use crate::tests::{check_diagnostics_with_disabled, check_fix};
#[test]
fn remove_unnecessary_else_for_return() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
return bar;
} else {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
}
}
"#,
&["needless_return", "E0425"],
);
check_fix(
r#"
fn test() {
if foo {
return bar;
} else$0 {
do_something_else();
}
}
"#,
r#"
fn test() {
if foo {
return bar;
}
do_something_else();
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_return2() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
return bar;
} else if qux {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
} else {
do_something_else2();
}
}
"#,
&["needless_return", "E0425"],
);
check_fix(
r#"
fn test() {
if foo {
return bar;
} else$0 if qux {
do_something_else();
} else {
do_something_else2();
}
}
"#,
r#"
fn test() {
if foo {
return bar;
}
if qux {
do_something_else();
} else {
do_something_else2();
}
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_return3() {
check_diagnostics_with_disabled(
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
} else {
//^^^^ 💡 weak: remove unnecessary else block
0
}
}
"#,
&["needless_return", "E0425"],
);
check_fix(
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
} else$0 {
0
}
}
"#,
r#"
fn test(a: bool) -> i32 {
if a {
return 1;
}
0
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_return_in_child_if_expr() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
do_something();
} else if qux {
return bar;
} else {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
}
}
"#,
&["needless_return", "E0425"],
);
check_fix(
r#"
fn test() {
if foo {
do_something();
} else if qux {
return bar;
} else$0 {
do_something_else();
}
}
"#,
r#"
fn test() {
if foo {
do_something();
} else {
if qux {
return bar;
}
do_something_else();
}
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_return_in_child_if_expr2() {
check_fix(
r#"
fn test() {
if foo {
do_something();
} else if qux {
return bar;
} else$0 if quux {
do_something_else();
} else {
do_something_else2();
}
}
"#,
r#"
fn test() {
if foo {
do_something();
} else {
if qux {
return bar;
}
if quux {
do_something_else();
} else {
do_something_else2();
}
}
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_break() {
check_diagnostics_with_disabled(
r#"
fn test() {
loop {
if foo {
break;
} else {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
}
}
}
"#,
&["E0425"],
);
check_fix(
r#"
fn test() {
loop {
if foo {
break;
} else$0 {
do_something_else();
}
}
}
"#,
r#"
fn test() {
loop {
if foo {
break;
}
do_something_else();
}
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_continue() {
check_diagnostics_with_disabled(
r#"
fn test() {
loop {
if foo {
continue;
} else {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
}
}
}
"#,
&["E0425"],
);
check_fix(
r#"
fn test() {
loop {
if foo {
continue;
} else$0 {
do_something_else();
}
}
}
"#,
r#"
fn test() {
loop {
if foo {
continue;
}
do_something_else();
}
}
"#,
);
}
#[test]
fn remove_unnecessary_else_for_never() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
never();
} else {
//^^^^ 💡 weak: remove unnecessary else block
do_something_else();
}
}
fn never() -> ! {
loop {}
}
"#,
&["E0425"],
);
check_fix(
r#"
fn test() {
if foo {
never();
} else$0 {
do_something_else();
}
}
fn never() -> ! {
loop {}
}
"#,
r#"
fn test() {
if foo {
never();
}
do_something_else();
}
fn never() -> ! {
loop {}
}
"#,
);
}
#[test]
fn no_diagnostic_if_no_else_branch() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
return bar;
}
do_something_else();
}
"#,
&["E0425"],
);
}
#[test]
fn no_diagnostic_if_no_divergence() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
do_something();
} else {
do_something_else();
}
}
"#,
&["E0425"],
);
}
#[test]
fn no_diagnostic_if_no_divergence_in_else_branch() {
check_diagnostics_with_disabled(
r#"
fn test() {
if foo {
do_something();
} else {
return bar;
}
}
"#,
&["needless_return", "E0425"],
);
}
#[test]
fn no_diagnostic_if_not_expr_stmt() {
check_diagnostics_with_disabled(
r#"
fn test1() {
let _x = if a {
return;
} else {
1
};
}
fn test2() {
let _x = if a {
return;
} else if b {
return;
} else if c {
1
} else {
return;
};
}
"#,
&["needless_return", "E0425"],
);
check_diagnostics_with_disabled(
r#"
fn test3() -> u8 {
foo(if a { return 1 } else { 0 })
}
"#,
&["E0425"],
);
}
}