ide_assists/handlers/
bind_unused_param.rs

1use crate::assist_context::{AssistContext, Assists};
2use ide_db::{LineIndexDatabase, assists::AssistId, defs::Definition};
3use syntax::{
4    AstNode,
5    ast::{self, HasName, edit_in_place::Indent},
6};
7
8// Assist: bind_unused_param
9//
10// Binds unused function parameter to an underscore.
11//
12// ```
13// fn some_function(x: i32$0) {}
14// ```
15// ->
16// ```
17// fn some_function(x: i32) {
18//     let _ = x;
19// }
20// ```
21pub(crate) fn bind_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
22    let param: ast::Param = ctx.find_node_at_offset()?;
23
24    let Some(ast::Pat::IdentPat(ident_pat)) = param.pat() else { return None };
25    let name = ident_pat.name().filter(|n| !n.text().starts_with('_'))?;
26
27    let param_def = {
28        let local = ctx.sema.to_def(&ident_pat)?;
29        Definition::Local(local)
30    };
31    if param_def.usages(&ctx.sema).at_least_one() {
32        cov_mark::hit!(keep_used);
33        return None;
34    }
35
36    let func = param.syntax().ancestors().nth(2).and_then(ast::Fn::cast)?;
37    let stmt_list = func.body()?.stmt_list()?;
38    let l_curly_range = stmt_list.l_curly_token()?.text_range();
39    let r_curly_range = stmt_list.r_curly_token()?.text_range();
40
41    acc.add(
42        AssistId::quick_fix("bind_unused_param"),
43        format!("Bind as `let _ = {name};`"),
44        param.syntax().text_range(),
45        |builder| {
46            let line_index = ctx.db().line_index(ctx.vfs_file_id());
47
48            let indent = func.indent_level();
49            let text_indent = indent + 1;
50            let mut text = format!("\n{text_indent}let _ = {name};");
51
52            let left_line = line_index.line_col(l_curly_range.end()).line;
53            let right_line = line_index.line_col(r_curly_range.start()).line;
54
55            if left_line == right_line {
56                cov_mark::hit!(single_line);
57                text.push_str(&format!("\n{indent}"));
58            }
59
60            builder.insert(l_curly_range.end(), text);
61        },
62    )
63}
64
65#[cfg(test)]
66mod tests {
67    use crate::tests::{check_assist, check_assist_not_applicable};
68
69    use super::*;
70
71    #[test]
72    fn bind_unused_empty_block() {
73        cov_mark::check!(single_line);
74        check_assist(
75            bind_unused_param,
76            r#"
77fn foo($0y: i32) {}
78"#,
79            r#"
80fn foo(y: i32) {
81    let _ = y;
82}
83"#,
84        );
85    }
86
87    #[test]
88    fn bind_unused_ref_ident_pat() {
89        cov_mark::check!(single_line);
90        check_assist(
91            bind_unused_param,
92            r#"
93fn foo(ref $0y: i32) {}
94"#,
95            r#"
96fn foo(ref y: i32) {
97    let _ = y;
98}
99"#,
100        );
101    }
102
103    #[test]
104    fn bind_unused_empty_block_with_newline() {
105        check_assist(
106            bind_unused_param,
107            r#"
108fn foo($0y: i32) {
109}
110"#,
111            r#"
112fn foo(y: i32) {
113    let _ = y;
114}
115"#,
116        );
117    }
118
119    #[test]
120    fn bind_unused_generic() {
121        check_assist(
122            bind_unused_param,
123            r#"
124fn foo<T>($0y: T)
125where T : Default {
126}
127"#,
128            r#"
129fn foo<T>(y: T)
130where T : Default {
131    let _ = y;
132}
133"#,
134        );
135    }
136
137    #[test]
138    fn trait_impl() {
139        check_assist(
140            bind_unused_param,
141            r#"
142trait Trait {
143    fn foo(x: i32);
144}
145impl Trait for () {
146    fn foo($0x: i32) {}
147}
148"#,
149            r#"
150trait Trait {
151    fn foo(x: i32);
152}
153impl Trait for () {
154    fn foo(x: i32) {
155        let _ = x;
156    }
157}
158"#,
159        );
160    }
161
162    #[test]
163    fn keep_used() {
164        cov_mark::check!(keep_used);
165        check_assist_not_applicable(
166            bind_unused_param,
167            r#"
168fn foo(x: i32, $0y: i32) { y; }
169"#,
170        );
171    }
172
173    #[test]
174    fn keep_underscore_used() {
175        check_assist_not_applicable(
176            bind_unused_param,
177            r#"
178fn foo($0_x: i32, y: i32) {}
179"#,
180        );
181    }
182
183    #[test]
184    fn not_applicable_closure() {
185        check_assist_not_applicable(
186            bind_unused_param,
187            r#"
188fn foo() {
189    let _ = |$0x| 2;
190}
191"#,
192        );
193    }
194}