]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/remove_unused_param.rs
add: fix: Adding remove_unused_param for method and fixing same for associative...
[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     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 self param into consideration, hence this additional check is
57     // added. There are two cases to handle in this scenario, where functions are
58     // associative(functions not associative and not containting contain self, are not allowed), in
59     // this case param position is rightly set. If a method call is present which has self param,
60     // that needs to be handled and is added below in process_usage function to reduce this increment and
61     // not consider self param.
62     if is_self_present {
63         param_position += 1;
64     }
65     let fn_def = {
66         let func = ctx.sema.to_def(&func)?;
67         Definition::ModuleDef(func.into())
68     };
69
70     let param_def = {
71         let local = ctx.sema.to_def(&ident_pat)?;
72         Definition::Local(local)
73     };
74     if param_def.usages(&ctx.sema).at_least_one() {
75         cov_mark::hit!(keep_used);
76         return None;
77     }
78     acc.add(
79         AssistId("remove_unused_param", AssistKind::Refactor),
80         "Remove unused parameter",
81         param.syntax().text_range(),
82         |builder| {
83             builder.delete(range_to_remove(param.syntax()));
84             for (file_id, references) in fn_def.usages(&ctx.sema).all() {
85                 process_usages(ctx, builder, file_id, references, param_position, is_self_present);
86             }
87         },
88     )
89 }
90
91 fn process_usages(
92     ctx: &AssistContext,
93     builder: &mut AssistBuilder,
94     file_id: FileId,
95     references: Vec<FileReference>,
96     arg_to_remove: usize,
97     is_self_present: bool,
98 ) {
99     let source_file = ctx.sema.parse(file_id);
100     builder.edit_file(file_id);
101     for usage in references {
102         if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove, is_self_present)
103         {
104             builder.delete(text_range);
105         }
106     }
107 }
108
109 fn process_usage(
110     source_file: &SourceFile,
111     FileReference { range, .. }: FileReference,
112     mut arg_to_remove: usize,
113     is_self_present: bool,
114 ) -> Option<TextRange> {
115     let call_expr_opt: Option<ast::CallExpr> = find_node_at_range(source_file.syntax(), range);
116     if let Some(call_expr) = call_expr_opt {
117         let call_expr_range = call_expr.expr()?.syntax().text_range();
118         if !call_expr_range.contains_range(range) {
119             return None;
120         }
121
122         let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
123         return Some(range_to_remove(arg.syntax()));
124     }
125
126     let method_call_expr_opt: Option<ast::MethodCallExpr> =
127         find_node_at_range(source_file.syntax(), range);
128     if let Some(method_call_expr) = method_call_expr_opt {
129         let method_call_expr_range = method_call_expr.name_ref()?.syntax().text_range();
130         if !method_call_expr_range.contains_range(range) {
131             return None;
132         }
133
134         if is_self_present {
135             arg_to_remove -= 1;
136         }
137
138         let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?;
139         return Some(range_to_remove(arg.syntax()));
140     }
141
142     return None;
143 }
144
145 fn range_to_remove(node: &SyntaxNode) -> TextRange {
146     let up_to_comma = next_prev().find_map(|dir| {
147         node.siblings_with_tokens(dir)
148             .filter_map(|it| it.into_token())
149             .find(|it| it.kind() == T![,])
150             .map(|it| (dir, it))
151     });
152     if let Some((dir, token)) = up_to_comma {
153         if node.next_sibling().is_some() {
154             let up_to_space = token
155                 .siblings_with_tokens(dir)
156                 .skip(1)
157                 .take_while(|it| it.kind() == WHITESPACE)
158                 .last()
159                 .and_then(|it| it.into_token());
160             return node
161                 .text_range()
162                 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
163         }
164         node.text_range().cover(token.text_range())
165     } else {
166         node.text_range()
167     }
168 }
169
170 #[cfg(test)]
171 mod tests {
172     use crate::tests::{check_assist, check_assist_not_applicable};
173
174     use super::*;
175
176     #[test]
177     fn remove_unused() {
178         check_assist(
179             remove_unused_param,
180             r#"
181 fn a() { foo(9, 2) }
182 fn foo(x: i32, $0y: i32) { x; }
183 fn b() { foo(9, 2,) }
184 "#,
185             r#"
186 fn a() { foo(9) }
187 fn foo(x: i32) { x; }
188 fn b() { foo(9, ) }
189 "#,
190         );
191     }
192
193     #[test]
194     fn remove_unused_first_param() {
195         check_assist(
196             remove_unused_param,
197             r#"
198 fn foo($0x: i32, y: i32) { y; }
199 fn a() { foo(1, 2) }
200 fn b() { foo(1, 2,) }
201 "#,
202             r#"
203 fn foo(y: i32) { y; }
204 fn a() { foo(2) }
205 fn b() { foo(2,) }
206 "#,
207         );
208     }
209
210     #[test]
211     fn remove_unused_single_param() {
212         check_assist(
213             remove_unused_param,
214             r#"
215 fn foo($0x: i32) { 0; }
216 fn a() { foo(1) }
217 fn b() { foo(1, ) }
218 "#,
219             r#"
220 fn foo() { 0; }
221 fn a() { foo() }
222 fn b() { foo( ) }
223 "#,
224         );
225     }
226
227     #[test]
228     fn remove_unused_surrounded_by_parms() {
229         check_assist(
230             remove_unused_param,
231             r#"
232 fn foo(x: i32, $0y: i32, z: i32) { x; }
233 fn a() { foo(1, 2, 3) }
234 fn b() { foo(1, 2, 3,) }
235 "#,
236             r#"
237 fn foo(x: i32, z: i32) { x; }
238 fn a() { foo(1, 3) }
239 fn b() { foo(1, 3,) }
240 "#,
241         );
242     }
243
244     #[test]
245     fn remove_unused_qualified_call() {
246         check_assist(
247             remove_unused_param,
248             r#"
249 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
250 fn b() { bar::foo(9, 2) }
251 "#,
252             r#"
253 mod bar { pub fn foo(x: i32) { x; } }
254 fn b() { bar::foo(9) }
255 "#,
256         );
257     }
258
259     #[test]
260     fn remove_unused_turbofished_func() {
261         check_assist(
262             remove_unused_param,
263             r#"
264 pub fn foo<T>(x: T, $0y: i32) { x; }
265 fn b() { foo::<i32>(9, 2) }
266 "#,
267             r#"
268 pub fn foo<T>(x: T) { x; }
269 fn b() { foo::<i32>(9) }
270 "#,
271         );
272     }
273
274     #[test]
275     fn remove_unused_generic_unused_param_func() {
276         check_assist(
277             remove_unused_param,
278             r#"
279 pub fn foo<T>(x: i32, $0y: T) { x; }
280 fn b() { foo::<i32>(9, 2) }
281 fn b2() { foo(9, 2) }
282 "#,
283             r#"
284 pub fn foo<T>(x: i32) { x; }
285 fn b() { foo::<i32>(9) }
286 fn b2() { foo(9) }
287 "#,
288         );
289     }
290
291     #[test]
292     fn keep_used() {
293         cov_mark::check!(keep_used);
294         check_assist_not_applicable(
295             remove_unused_param,
296             r#"
297 fn foo(x: i32, $0y: i32) { y; }
298 fn main() { foo(9, 2) }
299 "#,
300         );
301     }
302
303     #[test]
304     fn trait_impl() {
305         cov_mark::check!(trait_impl);
306         check_assist_not_applicable(
307             remove_unused_param,
308             r#"
309 trait Trait {
310     fn foo(x: i32);
311 }
312 impl Trait for () {
313     fn foo($0x: i32) {}
314 }
315 "#,
316         );
317     }
318
319     #[test]
320     fn remove_across_files() {
321         check_assist(
322             remove_unused_param,
323             r#"
324 //- /main.rs
325 fn foo(x: i32, $0y: i32) { x; }
326
327 mod foo;
328
329 //- /foo.rs
330 use super::foo;
331
332 fn bar() {
333     let _ = foo(1, 2);
334 }
335 "#,
336             r#"
337 //- /main.rs
338 fn foo(x: i32) { x; }
339
340 mod foo;
341
342 //- /foo.rs
343 use super::foo;
344
345 fn bar() {
346     let _ = foo(1);
347 }
348 "#,
349         )
350     }
351
352     #[test]
353     fn test_remove_method_param() {
354         check_assist(
355             remove_unused_param,
356             r#"
357 struct S;
358 impl S { fn f(&self, $0_unused: i32) {} }
359 fn main() {
360     S.f(92);
361     S.f();
362     S.f(93, 92);
363     S::f(&S, 92);
364 }
365 "#,
366             r#"
367 struct S;
368 impl S { fn f(&self) {} }
369 fn main() {
370     S.f();
371     S.f();
372     S.f(92);
373     S::f(&S);
374 }
375 "#,
376         )
377     }
378 }