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