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 // ```
remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Option<()>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 
process_usages( ctx: &AssistContext, builder: &mut AssistBuilder, file_id: FileId, references: Vec<FileReference>, arg_to_remove: usize, is_self_present: bool, )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     for usage in references {
100         if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove, is_self_present)
101         {
102             builder.delete(text_range);
103         }
104     }
105 }
106 
107 fn process_usage(
108     source_file: &SourceFile,
109     FileReference { range, .. }: FileReference,
110     mut arg_to_remove: usize,
111     is_self_present: bool,
112 ) -> Option<TextRange> {
113     let call_expr_opt: Option<ast::CallExpr> = find_node_at_range(source_file.syntax(), range);
114     if let Some(call_expr) = call_expr_opt {
115         let call_expr_range = call_expr.expr()?.syntax().text_range();
116         if !call_expr_range.contains_range(range) {
117             return None;
118         }
119 
120         let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
121         return Some(range_to_remove(arg.syntax()));
122     }
123 
124     let method_call_expr_opt: Option<ast::MethodCallExpr> =
125         find_node_at_range(source_file.syntax(), range);
126     if let Some(method_call_expr) = method_call_expr_opt {
127         let method_call_expr_range = method_call_expr.name_ref()?.syntax().text_range();
128         if !method_call_expr_range.contains_range(range) {
129             return None;
130         }
131 
132         if is_self_present {
133             arg_to_remove -= 1;
134         }
135 
136         let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?;
137         return Some(range_to_remove(arg.syntax()));
138     }
139 
140     None
141 }
142 
range_to_remove(node: &SyntaxNode) -> TextRange143 pub(crate) fn range_to_remove(node: &SyntaxNode) -> TextRange {
144     let up_to_comma = next_prev().find_map(|dir| {
145         node.siblings_with_tokens(dir)
146             .filter_map(|it| it.into_token())
147             .find(|it| it.kind() == T![,])
148             .map(|it| (dir, it))
149     });
150     if let Some((dir, token)) = up_to_comma {
151         if node.next_sibling().is_some() {
152             let up_to_space = token
153                 .siblings_with_tokens(dir)
154                 .skip(1)
155                 .take_while(|it| it.kind() == WHITESPACE)
156                 .last()
157                 .and_then(|it| it.into_token());
158             return node
159                 .text_range()
160                 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
161         }
162         node.text_range().cover(token.text_range())
163     } else {
164         node.text_range()
165     }
166 }
167 
168 #[cfg(test)]
169 mod tests {
170     use crate::tests::{check_assist, check_assist_not_applicable};
171 
172     use super::*;
173 
174     #[test]
remove_unused()175     fn remove_unused() {
176         check_assist(
177             remove_unused_param,
178             r#"
179 fn a() { foo(9, 2) }
180 fn foo(x: i32, $0y: i32) { x; }
181 fn b() { foo(9, 2,) }
182 "#,
183             r#"
184 fn a() { foo(9) }
185 fn foo(x: i32) { x; }
186 fn b() { foo(9, ) }
187 "#,
188         );
189     }
190 
191     #[test]
remove_unused_first_param()192     fn remove_unused_first_param() {
193         check_assist(
194             remove_unused_param,
195             r#"
196 fn foo($0x: i32, y: i32) { y; }
197 fn a() { foo(1, 2) }
198 fn b() { foo(1, 2,) }
199 "#,
200             r#"
201 fn foo(y: i32) { y; }
202 fn a() { foo(2) }
203 fn b() { foo(2,) }
204 "#,
205         );
206     }
207 
208     #[test]
remove_unused_single_param()209     fn remove_unused_single_param() {
210         check_assist(
211             remove_unused_param,
212             r#"
213 fn foo($0x: i32) { 0; }
214 fn a() { foo(1) }
215 fn b() { foo(1, ) }
216 "#,
217             r#"
218 fn foo() { 0; }
219 fn a() { foo() }
220 fn b() { foo( ) }
221 "#,
222         );
223     }
224 
225     #[test]
remove_unused_surrounded_by_parms()226     fn remove_unused_surrounded_by_parms() {
227         check_assist(
228             remove_unused_param,
229             r#"
230 fn foo(x: i32, $0y: i32, z: i32) { x; }
231 fn a() { foo(1, 2, 3) }
232 fn b() { foo(1, 2, 3,) }
233 "#,
234             r#"
235 fn foo(x: i32, z: i32) { x; }
236 fn a() { foo(1, 3) }
237 fn b() { foo(1, 3,) }
238 "#,
239         );
240     }
241 
242     #[test]
remove_unused_qualified_call()243     fn remove_unused_qualified_call() {
244         check_assist(
245             remove_unused_param,
246             r#"
247 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
248 fn b() { bar::foo(9, 2) }
249 "#,
250             r#"
251 mod bar { pub fn foo(x: i32) { x; } }
252 fn b() { bar::foo(9) }
253 "#,
254         );
255     }
256 
257     #[test]
remove_unused_turbofished_func()258     fn remove_unused_turbofished_func() {
259         check_assist(
260             remove_unused_param,
261             r#"
262 pub fn foo<T>(x: T, $0y: i32) { x; }
263 fn b() { foo::<i32>(9, 2) }
264 "#,
265             r#"
266 pub fn foo<T>(x: T) { x; }
267 fn b() { foo::<i32>(9) }
268 "#,
269         );
270     }
271 
272     #[test]
remove_unused_generic_unused_param_func()273     fn remove_unused_generic_unused_param_func() {
274         check_assist(
275             remove_unused_param,
276             r#"
277 pub fn foo<T>(x: i32, $0y: T) { x; }
278 fn b() { foo::<i32>(9, 2) }
279 fn b2() { foo(9, 2) }
280 "#,
281             r#"
282 pub fn foo<T>(x: i32) { x; }
283 fn b() { foo::<i32>(9) }
284 fn b2() { foo(9) }
285 "#,
286         );
287     }
288 
289     #[test]
keep_used()290     fn keep_used() {
291         cov_mark::check!(keep_used);
292         check_assist_not_applicable(
293             remove_unused_param,
294             r#"
295 fn foo(x: i32, $0y: i32) { y; }
296 fn main() { foo(9, 2) }
297 "#,
298         );
299     }
300 
301     #[test]
trait_impl()302     fn trait_impl() {
303         cov_mark::check!(trait_impl);
304         check_assist_not_applicable(
305             remove_unused_param,
306             r#"
307 trait Trait {
308     fn foo(x: i32);
309 }
310 impl Trait for () {
311     fn foo($0x: i32) {}
312 }
313 "#,
314         );
315     }
316 
317     #[test]
remove_across_files()318     fn remove_across_files() {
319         check_assist(
320             remove_unused_param,
321             r#"
322 //- /main.rs
323 fn foo(x: i32, $0y: i32) { x; }
324 
325 mod foo;
326 
327 //- /foo.rs
328 use super::foo;
329 
330 fn bar() {
331     let _ = foo(1, 2);
332 }
333 "#,
334             r#"
335 //- /main.rs
336 fn foo(x: i32) { x; }
337 
338 mod foo;
339 
340 //- /foo.rs
341 use super::foo;
342 
343 fn bar() {
344     let _ = foo(1);
345 }
346 "#,
347         )
348     }
349 
350     #[test]
test_remove_method_param()351     fn test_remove_method_param() {
352         check_assist(
353             remove_unused_param,
354             r#"
355 struct S;
356 impl S { fn f(&self, $0_unused: i32) {} }
357 fn main() {
358     S.f(92);
359     S.f();
360     S.f(93, 92);
361     S::f(&S, 92);
362 }
363 "#,
364             r#"
365 struct S;
366 impl S { fn f(&self) {} }
367 fn main() {
368     S.f();
369     S.f();
370     S.f(92);
371     S::f(&S);
372 }
373 "#,
374         )
375     }
376 }
377