blob: d831878044d32d2e824074a29e405ed4f9be0a90 [file] [log] [blame]
use hir::{db::ExpandDatabase, diagnostics::RemoveTrailingReturn};
use ide_db::{assists::Assist, base_db::FileRange, source_change::SourceChange};
use syntax::{ast, AstNode};
use text_edit::TextEdit;
use crate::{adjusted_display_range, fix, Diagnostic, DiagnosticCode, DiagnosticsContext};
// Diagnostic: remove-trailing-return
//
// This diagnostic is triggered when there is a redundant `return` at the end of a function
// or closure.
pub(crate) fn remove_trailing_return(
ctx: &DiagnosticsContext<'_>,
d: &RemoveTrailingReturn,
) -> Option<Diagnostic> {
if d.return_expr.file_id.macro_file().is_some() {
// FIXME: Our infra can't handle allow from within macro expansions rn
return None;
}
let display_range = adjusted_display_range(ctx, d.return_expr, &|return_expr| {
return_expr
.syntax()
.parent()
.and_then(ast::ExprStmt::cast)
.map(|stmt| stmt.syntax().text_range())
});
Some(
Diagnostic::new(
DiagnosticCode::Clippy("needless_return"),
"replace return <expr>; with <expr>",
display_range,
)
.with_fixes(fixes(ctx, d)),
)
}
fn fixes(ctx: &DiagnosticsContext<'_>, d: &RemoveTrailingReturn) -> Option<Vec<Assist>> {
let root = ctx.sema.db.parse_or_expand(d.return_expr.file_id);
let return_expr = d.return_expr.value.to_node(&root);
let stmt = return_expr.syntax().parent().and_then(ast::ExprStmt::cast);
let FileRange { range, file_id } =
ctx.sema.original_range_opt(stmt.as_ref().map_or(return_expr.syntax(), AstNode::syntax))?;
if Some(file_id) != d.return_expr.file_id.file_id() {
return None;
}
let replacement =
return_expr.expr().map_or_else(String::new, |expr| format!("{}", expr.syntax().text()));
let edit = TextEdit::replace(range, replacement);
let source_change = SourceChange::from_text_edit(file_id, edit);
Some(vec![fix(
"remove_trailing_return",
"Replace return <expr>; with <expr>",
source_change,
range,
)])
}
#[cfg(test)]
mod tests {
use crate::tests::{
check_diagnostics, check_diagnostics_with_disabled, check_fix, check_fix_with_disabled,
};
#[test]
fn remove_trailing_return() {
check_diagnostics(
r#"
fn foo() -> u8 {
return 2;
} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
"#,
);
}
#[test]
fn remove_trailing_return_inner_function() {
check_diagnostics(
r#"
fn foo() -> u8 {
fn bar() -> u8 {
return 2;
} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
bar()
}
"#,
);
}
#[test]
fn remove_trailing_return_closure() {
check_diagnostics(
r#"
fn foo() -> u8 {
let bar = || return 2;
bar() //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
}
"#,
);
check_diagnostics(
r#"
fn foo() -> u8 {
let bar = || {
return 2;
};//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
bar()
}
"#,
);
}
#[test]
fn remove_trailing_return_unit() {
check_diagnostics(
r#"
fn foo() {
return
} //^^^^^^ 💡 weak: replace return <expr>; with <expr>
"#,
);
}
#[test]
fn remove_trailing_return_no_semi() {
check_diagnostics(
r#"
fn foo() -> u8 {
return 2
} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
"#,
);
}
#[test]
fn remove_trailing_return_in_if() {
check_diagnostics_with_disabled(
r#"
fn foo(x: usize) -> u8 {
if x > 0 {
return 1;
//^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
} else {
return 0;
} //^^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
}
"#,
&["remove-unnecessary-else"],
);
}
#[test]
fn remove_trailing_return_in_match() {
check_diagnostics(
r#"
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => return 1,
//^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
Err(_) => return 0,
} //^^^^^^^^ 💡 weak: replace return <expr>; with <expr>
}
"#,
);
}
#[test]
fn no_diagnostic_if_no_return_keyword() {
check_diagnostics(
r#"
fn foo() -> u8 {
3
}
"#,
);
}
#[test]
fn no_diagnostic_if_not_last_statement() {
check_diagnostics(
r#"
fn foo() -> u8 {
if true { return 2; }
3
}
"#,
);
}
#[test]
fn no_diagnostic_if_not_last_statement2() {
check_diagnostics(
r#"
fn foo() -> u8 {
return 2;
fn bar() {}
}
"#,
);
}
#[test]
fn replace_with_expr() {
check_fix(
r#"
fn foo() -> u8 {
return$0 2;
}
"#,
r#"
fn foo() -> u8 {
2
}
"#,
);
}
#[test]
fn replace_with_unit() {
check_fix(
r#"
fn foo() {
return$0/*ensure tidy is happy*/
}
"#,
r#"
fn foo() {
/*ensure tidy is happy*/
}
"#,
);
}
#[test]
fn replace_with_expr_no_semi() {
check_fix(
r#"
fn foo() -> u8 {
return$0 2
}
"#,
r#"
fn foo() -> u8 {
2
}
"#,
);
}
#[test]
fn replace_in_inner_function() {
check_fix(
r#"
fn foo() -> u8 {
fn bar() -> u8 {
return$0 2;
}
bar()
}
"#,
r#"
fn foo() -> u8 {
fn bar() -> u8 {
2
}
bar()
}
"#,
);
}
#[test]
fn replace_in_closure() {
check_fix(
r#"
fn foo() -> u8 {
let bar = || return$0 2;
bar()
}
"#,
r#"
fn foo() -> u8 {
let bar = || 2;
bar()
}
"#,
);
check_fix(
r#"
fn foo() -> u8 {
let bar = || {
return$0 2;
};
bar()
}
"#,
r#"
fn foo() -> u8 {
let bar = || {
2
};
bar()
}
"#,
);
}
#[test]
fn replace_in_if() {
check_fix_with_disabled(
r#"
fn foo(x: usize) -> u8 {
if x > 0 {
return$0 1;
} else {
0
}
}
"#,
r#"
fn foo(x: usize) -> u8 {
if x > 0 {
1
} else {
0
}
}
"#,
std::iter::once("remove-unnecessary-else".to_owned()),
);
check_fix(
r#"
fn foo(x: usize) -> u8 {
if x > 0 {
1
} else {
return$0 0;
}
}
"#,
r#"
fn foo(x: usize) -> u8 {
if x > 0 {
1
} else {
0
}
}
"#,
);
}
#[test]
fn replace_in_match() {
check_fix(
r#"
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => return$0 1,
Err(_) => 0,
}
}
"#,
r#"
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => 1,
Err(_) => 0,
}
}
"#,
);
check_fix(
r#"
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => 1,
Err(_) => return$0 0,
}
}
"#,
r#"
fn foo<T, E>(x: Result<T, E>) -> u8 {
match x {
Ok(_) => 1,
Err(_) => 0,
}
}
"#,
);
}
}