]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/remove_unused_param.rs
Merge #9209
[rust.git] / crates / ide_assists / src / handlers / remove_unused_param.rs
1 use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2 use syntax::{
3     algo::find_node_at_range,
4     ast::{self, ArgListOwner},
5     AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
6 };
7
8 use SyntaxKind::WHITESPACE;
9
10 use crate::{
11     assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
12 };
13
14 // Assist: remove_unused_param
15 //
16 // Removes unused function parameter.
17 //
18 // ```
19 // fn frobnicate(x: i32$0) {}
20 //
21 // fn main() {
22 //     frobnicate(92);
23 // }
24 // ```
25 // ->
26 // ```
27 // fn frobnicate() {}
28 //
29 // fn main() {
30 //     frobnicate();
31 // }
32 // ```
33 pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
34     let param: ast::Param = ctx.find_node_at_offset()?;
35     let ident_pat = match param.pat()? {
36         ast::Pat::IdentPat(it) => it,
37         _ => return None,
38     };
39     let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
40
41     // check if fn is in impl Trait for ..
42     if func
43         .syntax()
44         .parent() // AssocItemList
45         .and_then(|x| x.parent())
46         .and_then(ast::Impl::cast)
47         .map_or(false, |imp| imp.trait_().is_some())
48     {
49         cov_mark::hit!(trait_impl);
50         return None;
51     }
52
53     let param_position = func.param_list()?.params().position(|it| it == param)?;
54     let fn_def = {
55         let func = ctx.sema.to_def(&func)?;
56         Definition::ModuleDef(func.into())
57     };
58
59     let param_def = {
60         let local = ctx.sema.to_def(&ident_pat)?;
61         Definition::Local(local)
62     };
63     if param_def.usages(&ctx.sema).at_least_one() {
64         cov_mark::hit!(keep_used);
65         return None;
66     }
67     acc.add(
68         AssistId("remove_unused_param", AssistKind::Refactor),
69         "Remove unused parameter",
70         param.syntax().text_range(),
71         |builder| {
72             builder.delete(range_to_remove(param.syntax()));
73             for (file_id, references) in fn_def.usages(&ctx.sema).all() {
74                 process_usages(ctx, builder, file_id, references, param_position);
75             }
76         },
77     )
78 }
79
80 fn process_usages(
81     ctx: &AssistContext,
82     builder: &mut AssistBuilder,
83     file_id: FileId,
84     references: Vec<FileReference>,
85     arg_to_remove: usize,
86 ) {
87     let source_file = ctx.sema.parse(file_id);
88     builder.edit_file(file_id);
89     for usage in references {
90         if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) {
91             builder.delete(text_range);
92         }
93     }
94 }
95
96 fn process_usage(
97     source_file: &SourceFile,
98     FileReference { range, .. }: FileReference,
99     arg_to_remove: usize,
100 ) -> Option<TextRange> {
101     let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
102     let call_expr_range = call_expr.expr()?.syntax().text_range();
103     if !call_expr_range.contains_range(range) {
104         return None;
105     }
106     let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
107     Some(range_to_remove(arg.syntax()))
108 }
109
110 fn range_to_remove(node: &SyntaxNode) -> TextRange {
111     let up_to_comma = next_prev().find_map(|dir| {
112         node.siblings_with_tokens(dir)
113             .filter_map(|it| it.into_token())
114             .find(|it| it.kind() == T![,])
115             .map(|it| (dir, it))
116     });
117     if let Some((dir, token)) = up_to_comma {
118         if node.next_sibling().is_some() {
119             let up_to_space = token
120                 .siblings_with_tokens(dir)
121                 .skip(1)
122                 .take_while(|it| it.kind() == WHITESPACE)
123                 .last()
124                 .and_then(|it| it.into_token());
125             return node
126                 .text_range()
127                 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
128         }
129         node.text_range().cover(token.text_range())
130     } else {
131         node.text_range()
132     }
133 }
134
135 #[cfg(test)]
136 mod tests {
137     use crate::tests::{check_assist, check_assist_not_applicable};
138
139     use super::*;
140
141     #[test]
142     fn remove_unused() {
143         check_assist(
144             remove_unused_param,
145             r#"
146 fn a() { foo(9, 2) }
147 fn foo(x: i32, $0y: i32) { x; }
148 fn b() { foo(9, 2,) }
149 "#,
150             r#"
151 fn a() { foo(9) }
152 fn foo(x: i32) { x; }
153 fn b() { foo(9, ) }
154 "#,
155         );
156     }
157
158     #[test]
159     fn remove_unused_first_param() {
160         check_assist(
161             remove_unused_param,
162             r#"
163 fn foo($0x: i32, y: i32) { y; }
164 fn a() { foo(1, 2) }
165 fn b() { foo(1, 2,) }
166 "#,
167             r#"
168 fn foo(y: i32) { y; }
169 fn a() { foo(2) }
170 fn b() { foo(2,) }
171 "#,
172         );
173     }
174
175     #[test]
176     fn remove_unused_single_param() {
177         check_assist(
178             remove_unused_param,
179             r#"
180 fn foo($0x: i32) { 0; }
181 fn a() { foo(1) }
182 fn b() { foo(1, ) }
183 "#,
184             r#"
185 fn foo() { 0; }
186 fn a() { foo() }
187 fn b() { foo( ) }
188 "#,
189         );
190     }
191
192     #[test]
193     fn remove_unused_surrounded_by_parms() {
194         check_assist(
195             remove_unused_param,
196             r#"
197 fn foo(x: i32, $0y: i32, z: i32) { x; }
198 fn a() { foo(1, 2, 3) }
199 fn b() { foo(1, 2, 3,) }
200 "#,
201             r#"
202 fn foo(x: i32, z: i32) { x; }
203 fn a() { foo(1, 3) }
204 fn b() { foo(1, 3,) }
205 "#,
206         );
207     }
208
209     #[test]
210     fn remove_unused_qualified_call() {
211         check_assist(
212             remove_unused_param,
213             r#"
214 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
215 fn b() { bar::foo(9, 2) }
216 "#,
217             r#"
218 mod bar { pub fn foo(x: i32) { x; } }
219 fn b() { bar::foo(9) }
220 "#,
221         );
222     }
223
224     #[test]
225     fn remove_unused_turbofished_func() {
226         check_assist(
227             remove_unused_param,
228             r#"
229 pub fn foo<T>(x: T, $0y: i32) { x; }
230 fn b() { foo::<i32>(9, 2) }
231 "#,
232             r#"
233 pub fn foo<T>(x: T) { x; }
234 fn b() { foo::<i32>(9) }
235 "#,
236         );
237     }
238
239     #[test]
240     fn remove_unused_generic_unused_param_func() {
241         check_assist(
242             remove_unused_param,
243             r#"
244 pub fn foo<T>(x: i32, $0y: T) { x; }
245 fn b() { foo::<i32>(9, 2) }
246 fn b2() { foo(9, 2) }
247 "#,
248             r#"
249 pub fn foo<T>(x: i32) { x; }
250 fn b() { foo::<i32>(9) }
251 fn b2() { foo(9) }
252 "#,
253         );
254     }
255
256     #[test]
257     fn keep_used() {
258         cov_mark::check!(keep_used);
259         check_assist_not_applicable(
260             remove_unused_param,
261             r#"
262 fn foo(x: i32, $0y: i32) { y; }
263 fn main() { foo(9, 2) }
264 "#,
265         );
266     }
267
268     #[test]
269     fn trait_impl() {
270         cov_mark::check!(trait_impl);
271         check_assist_not_applicable(
272             remove_unused_param,
273             r#"
274 trait Trait {
275     fn foo(x: i32);
276 }
277 impl Trait for () {
278     fn foo($0x: i32) {}
279 }
280 "#,
281         );
282     }
283
284     #[test]
285     fn remove_across_files() {
286         check_assist(
287             remove_unused_param,
288             r#"
289 //- /main.rs
290 fn foo(x: i32, $0y: i32) { x; }
291
292 mod foo;
293
294 //- /foo.rs
295 use super::foo;
296
297 fn bar() {
298     let _ = foo(1, 2);
299 }
300 "#,
301             r#"
302 //- /main.rs
303 fn foo(x: i32) { x; }
304
305 mod foo;
306
307 //- /foo.rs
308 use super::foo;
309
310 fn bar() {
311     let _ = foo(1);
312 }
313 "#,
314         )
315     }
316 }