]> git.lizzy.rs Git - rust.git/blob - src/tools/rust-analyzer/crates/ide-assists/src/handlers/remove_unused_param.rs
Rollup merge of #103996 - SUPERCILEX:docs, r=RalfJung
[rust.git] / src / tools / rust-analyzer / 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, HasArgList},
5     AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
6 };
7
8 use SyntaxKind::WHITESPACE;
9
10 use crate::{
11     assist_context::SourceChangeBuilder, utils::next_prev, AssistContext, AssistId, AssistKind,
12     Assists,
13 };
14
15 // Assist: remove_unused_param
16 //
17 // Removes unused function parameter.
18 //
19 // ```
20 // fn frobnicate(x: i32$0) {}
21 //
22 // fn main() {
23 //     frobnicate(92);
24 // }
25 // ```
26 // ->
27 // ```
28 // fn frobnicate() {}
29 //
30 // fn main() {
31 //     frobnicate();
32 // }
33 // ```
34 pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
35     let param: ast::Param = ctx.find_node_at_offset()?;
36     let ident_pat = match param.pat()? {
37         ast::Pat::IdentPat(it) => it,
38         _ => return None,
39     };
40     let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
41     let is_self_present =
42         param.syntax().parent()?.children().find_map(ast::SelfParam::cast).is_some();
43
44     // check if fn is in impl Trait for ..
45     if func
46         .syntax()
47         .parent() // AssocItemList
48         .and_then(|x| x.parent())
49         .and_then(ast::Impl::cast)
50         .map_or(false, |imp| imp.trait_().is_some())
51     {
52         cov_mark::hit!(trait_impl);
53         return None;
54     }
55
56     let mut param_position = func.param_list()?.params().position(|it| it == param)?;
57     // param_list() does not take the self param into consideration, hence this additional check
58     // is required. For associated functions, param_position is incremented here. For inherent
59     // calls we revet the increment below, in process_usage, as those calls will not have an
60     // explicit self parameter.
61     if is_self_present {
62         param_position += 1;
63     }
64     let fn_def = {
65         let func = ctx.sema.to_def(&func)?;
66         Definition::Function(func)
67     };
68
69     let param_def = {
70         let local = ctx.sema.to_def(&ident_pat)?;
71         Definition::Local(local)
72     };
73     if param_def.usages(&ctx.sema).at_least_one() {
74         cov_mark::hit!(keep_used);
75         return None;
76     }
77     acc.add(
78         AssistId("remove_unused_param", AssistKind::Refactor),
79         "Remove unused parameter",
80         param.syntax().text_range(),
81         |builder| {
82             builder.delete(range_to_remove(param.syntax()));
83             for (file_id, references) in fn_def.usages(&ctx.sema).all() {
84                 process_usages(ctx, builder, file_id, references, param_position, is_self_present);
85             }
86         },
87     )
88 }
89
90 fn process_usages(
91     ctx: &AssistContext<'_>,
92     builder: &mut SourceChangeBuilder,
93     file_id: FileId,
94     references: Vec<FileReference>,
95     arg_to_remove: usize,
96     is_self_present: bool,
97 ) {
98     let source_file = ctx.sema.parse(file_id);
99     builder.edit_file(file_id);
100     let possible_ranges = references
101         .into_iter()
102         .filter_map(|usage| process_usage(&source_file, usage, arg_to_remove, is_self_present));
103
104     let mut ranges_to_delete: Vec<TextRange> = vec![];
105     for range in possible_ranges {
106         if !ranges_to_delete.iter().any(|it| it.contains_range(range)) {
107             ranges_to_delete.push(range)
108         }
109     }
110
111     for range in ranges_to_delete {
112         builder.delete(range)
113     }
114 }
115
116 fn process_usage(
117     source_file: &SourceFile,
118     FileReference { range, .. }: FileReference,
119     mut arg_to_remove: usize,
120     is_self_present: bool,
121 ) -> Option<TextRange> {
122     let call_expr_opt: Option<ast::CallExpr> = find_node_at_range(source_file.syntax(), range);
123     if let Some(call_expr) = call_expr_opt {
124         let call_expr_range = call_expr.expr()?.syntax().text_range();
125         if !call_expr_range.contains_range(range) {
126             return None;
127         }
128
129         let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
130         return Some(range_to_remove(arg.syntax()));
131     }
132
133     let method_call_expr_opt: Option<ast::MethodCallExpr> =
134         find_node_at_range(source_file.syntax(), range);
135     if let Some(method_call_expr) = method_call_expr_opt {
136         let method_call_expr_range = method_call_expr.name_ref()?.syntax().text_range();
137         if !method_call_expr_range.contains_range(range) {
138             return None;
139         }
140
141         if is_self_present {
142             arg_to_remove -= 1;
143         }
144
145         let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?;
146         return Some(range_to_remove(arg.syntax()));
147     }
148
149     None
150 }
151
152 pub(crate) fn range_to_remove(node: &SyntaxNode) -> TextRange {
153     let up_to_comma = next_prev().find_map(|dir| {
154         node.siblings_with_tokens(dir)
155             .filter_map(|it| it.into_token())
156             .find(|it| it.kind() == T![,])
157             .map(|it| (dir, it))
158     });
159     if let Some((dir, token)) = up_to_comma {
160         if node.next_sibling().is_some() {
161             let up_to_space = token
162                 .siblings_with_tokens(dir)
163                 .skip(1)
164                 .take_while(|it| it.kind() == WHITESPACE)
165                 .last()
166                 .and_then(|it| it.into_token());
167             return node
168                 .text_range()
169                 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
170         }
171         node.text_range().cover(token.text_range())
172     } else {
173         node.text_range()
174     }
175 }
176
177 #[cfg(test)]
178 mod tests {
179     use crate::tests::{check_assist, check_assist_not_applicable};
180
181     use super::*;
182
183     #[test]
184     fn remove_unused() {
185         check_assist(
186             remove_unused_param,
187             r#"
188 fn a() { foo(9, 2) }
189 fn foo(x: i32, $0y: i32) { x; }
190 fn b() { foo(9, 2,) }
191 "#,
192             r#"
193 fn a() { foo(9) }
194 fn foo(x: i32) { x; }
195 fn b() { foo(9, ) }
196 "#,
197         );
198     }
199
200     #[test]
201     fn remove_unused_first_param() {
202         check_assist(
203             remove_unused_param,
204             r#"
205 fn foo($0x: i32, y: i32) { y; }
206 fn a() { foo(1, 2) }
207 fn b() { foo(1, 2,) }
208 "#,
209             r#"
210 fn foo(y: i32) { y; }
211 fn a() { foo(2) }
212 fn b() { foo(2,) }
213 "#,
214         );
215     }
216
217     #[test]
218     fn remove_unused_single_param() {
219         check_assist(
220             remove_unused_param,
221             r#"
222 fn foo($0x: i32) { 0; }
223 fn a() { foo(1) }
224 fn b() { foo(1, ) }
225 "#,
226             r#"
227 fn foo() { 0; }
228 fn a() { foo() }
229 fn b() { foo( ) }
230 "#,
231         );
232     }
233
234     #[test]
235     fn remove_unused_surrounded_by_parms() {
236         check_assist(
237             remove_unused_param,
238             r#"
239 fn foo(x: i32, $0y: i32, z: i32) { x; }
240 fn a() { foo(1, 2, 3) }
241 fn b() { foo(1, 2, 3,) }
242 "#,
243             r#"
244 fn foo(x: i32, z: i32) { x; }
245 fn a() { foo(1, 3) }
246 fn b() { foo(1, 3,) }
247 "#,
248         );
249     }
250
251     #[test]
252     fn remove_unused_qualified_call() {
253         check_assist(
254             remove_unused_param,
255             r#"
256 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
257 fn b() { bar::foo(9, 2) }
258 "#,
259             r#"
260 mod bar { pub fn foo(x: i32) { x; } }
261 fn b() { bar::foo(9) }
262 "#,
263         );
264     }
265
266     #[test]
267     fn remove_unused_turbofished_func() {
268         check_assist(
269             remove_unused_param,
270             r#"
271 pub fn foo<T>(x: T, $0y: i32) { x; }
272 fn b() { foo::<i32>(9, 2) }
273 "#,
274             r#"
275 pub fn foo<T>(x: T) { x; }
276 fn b() { foo::<i32>(9) }
277 "#,
278         );
279     }
280
281     #[test]
282     fn remove_unused_generic_unused_param_func() {
283         check_assist(
284             remove_unused_param,
285             r#"
286 pub fn foo<T>(x: i32, $0y: T) { x; }
287 fn b() { foo::<i32>(9, 2) }
288 fn b2() { foo(9, 2) }
289 "#,
290             r#"
291 pub fn foo<T>(x: i32) { x; }
292 fn b() { foo::<i32>(9) }
293 fn b2() { foo(9) }
294 "#,
295         );
296     }
297
298     #[test]
299     fn keep_used() {
300         cov_mark::check!(keep_used);
301         check_assist_not_applicable(
302             remove_unused_param,
303             r#"
304 fn foo(x: i32, $0y: i32) { y; }
305 fn main() { foo(9, 2) }
306 "#,
307         );
308     }
309
310     #[test]
311     fn trait_impl() {
312         cov_mark::check!(trait_impl);
313         check_assist_not_applicable(
314             remove_unused_param,
315             r#"
316 trait Trait {
317     fn foo(x: i32);
318 }
319 impl Trait for () {
320     fn foo($0x: i32) {}
321 }
322 "#,
323         );
324     }
325
326     #[test]
327     fn remove_across_files() {
328         check_assist(
329             remove_unused_param,
330             r#"
331 //- /main.rs
332 fn foo(x: i32, $0y: i32) { x; }
333
334 mod foo;
335
336 //- /foo.rs
337 use super::foo;
338
339 fn bar() {
340     let _ = foo(1, 2);
341 }
342 "#,
343             r#"
344 //- /main.rs
345 fn foo(x: i32) { x; }
346
347 mod foo;
348
349 //- /foo.rs
350 use super::foo;
351
352 fn bar() {
353     let _ = foo(1);
354 }
355 "#,
356         )
357     }
358
359     #[test]
360     fn test_remove_method_param() {
361         check_assist(
362             remove_unused_param,
363             r#"
364 struct S;
365 impl S { fn f(&self, $0_unused: i32) {} }
366 fn main() {
367     S.f(92);
368     S.f();
369     S.f(93, 92);
370     S::f(&S, 92);
371 }
372 "#,
373             r#"
374 struct S;
375 impl S { fn f(&self) {} }
376 fn main() {
377     S.f();
378     S.f();
379     S.f(92);
380     S::f(&S);
381 }
382 "#,
383         )
384     }
385
386     #[test]
387     fn nested_call() {
388         check_assist(
389             remove_unused_param,
390             r#"
391 fn foo(x: i32, $0y: i32) -> i32 {
392     x
393 }
394
395 fn bar() {
396     foo(1, foo(2, 3));
397 }
398 "#,
399             r#"
400 fn foo(x: i32) -> i32 {
401     x
402 }
403
404 fn bar() {
405     foo(1);
406 }
407 "#,
408         )
409     }
410 }