diff --git a/crates/ide-assists/src/handlers/bind_unused_param.rs b/crates/ide-assists/src/handlers/bind_unused_param.rs new file mode 100644 index 0000000000..45c1f0ccae --- /dev/null +++ b/crates/ide-assists/src/handlers/bind_unused_param.rs @@ -0,0 +1,159 @@ +use crate::assist_context::{AssistContext, Assists}; +use ide_db::{ + assists::{AssistId, AssistKind}, + defs::Definition, + LineIndexDatabase, +}; +use syntax::{ + ast::{self, edit_in_place::Indent}, + AstNode, +}; + +// Assist: bind_unused_param +// +// Binds unused function parameter to an underscore. +// +// ``` +// fn some_function(x: i32$0) {} +// ``` +// -> +// ``` +// fn some_function(x: i32) { +// let _ = x; +// } +// ``` +pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> { + let param: ast::Param = ctx.find_node_at_offset()?; + + let Some(ast::Pat::IdentPat(ident_pat)) = param.pat() else { return None }; + + let param_def = { + let local = ctx.sema.to_def(&ident_pat)?; + Definition::Local(local) + }; + if param_def.usages(&ctx.sema).at_least_one() { + cov_mark::hit!(keep_used); + return None; + } + + let func = param.syntax().ancestors().find_map(ast::Fn::cast)?; + let stmt_list = func.body()?.stmt_list()?; + let l_curly_range = stmt_list.l_curly_token()?.text_range(); + let r_curly_range = stmt_list.r_curly_token()?.text_range(); + + acc.add( + AssistId("bind_unused_param", AssistKind::QuickFix), + &format!("Bind as `let _ = {};`", &ident_pat), + param.syntax().text_range(), + |builder| { + let line_index = ctx.db().line_index(ctx.file_id()); + + let indent = func.indent_level(); + let text_indent = indent + 1; + let mut text = format!("\n{text_indent}let _ = {ident_pat};"); + + let left_line = line_index.line_col(l_curly_range.end()).line; + let right_line = line_index.line_col(r_curly_range.start()).line; + + if left_line == right_line { + cov_mark::hit!(single_line); + text.push_str(&format!("\n{indent}")); + } + + builder.insert(l_curly_range.end(), text); + }, + ) +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn bind_unused_empty_block() { + cov_mark::check!(single_line); + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) {} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_empty_block_with_newline() { + check_assist( + bind_unused_param, + r#" +fn foo($0y: i32) { +} +"#, + r#" +fn foo(y: i32) { + let _ = y; +} +"#, + ); + } + + #[test] + fn bind_unused_generic() { + check_assist( + bind_unused_param, + r#" +fn foo($0y: T) +where T : Default { +} +"#, + r#" +fn foo(y: T) +where T : Default { + let _ = y; +} +"#, + ); + } + + #[test] + fn trait_impl() { + check_assist( + bind_unused_param, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo($0x: i32) {} +} +"#, + r#" +trait Trait { + fn foo(x: i32); +} +impl Trait for () { + fn foo(x: i32) { + let _ = x; + } +} +"#, + ); + } + + #[test] + fn keep_used() { + cov_mark::check!(keep_used); + check_assist_not_applicable( + bind_unused_param, + r#" +fn foo(x: i32, $0y: i32) { y; } +"#, + ); + } +} diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs index 7136bdab21..6f973ab53e 100644 --- a/crates/ide-assists/src/lib.rs +++ b/crates/ide-assists/src/lib.rs @@ -114,6 +114,7 @@ mod handlers { mod add_turbo_fish; mod apply_demorgan; mod auto_import; + mod bind_unused_param; mod change_visibility; mod convert_bool_then; mod convert_comment_block; @@ -225,6 +226,7 @@ mod handlers { add_turbo_fish::add_turbo_fish, apply_demorgan::apply_demorgan, auto_import::auto_import, + bind_unused_param::bind_unused_param, change_visibility::change_visibility, convert_bool_then::convert_bool_then_to_if, convert_bool_then::convert_if_to_bool_then, diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs index cc132f9a98..dfaa53449f 100644 --- a/crates/ide-assists/src/tests/generated.rs +++ b/crates/ide-assists/src/tests/generated.rs @@ -265,6 +265,21 @@ pub mod std { pub mod collections { pub struct HashMap { } } } ) } +#[test] +fn doctest_bind_unused_param() { + check_doc_test( + "bind_unused_param", + r#####" +fn some_function(x: i32$0) {} +"#####, + r#####" +fn some_function(x: i32) { + let _ = x; +} +"#####, + ) +} + #[test] fn doctest_change_visibility() { check_doc_test(