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 // ```
remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()>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
process_usages( ctx: &AssistContext<'_>, builder: &mut SourceChangeBuilder, file_id: FileId, references: Vec<FileReference>, arg_to_remove: usize, is_self_present: bool, )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
range_to_remove(node: &SyntaxNode) -> TextRange152 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]
remove_unused()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]
remove_unused_first_param()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]
remove_unused_single_param()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]
remove_unused_surrounded_by_params()235 fn remove_unused_surrounded_by_params() {
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]
remove_unused_qualified_call()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]
remove_unused_turbofished_func()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]
remove_unused_generic_unused_param_func()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]
keep_used()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]
trait_impl()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]
remove_across_files()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]
test_remove_method_param()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]
nested_call()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 }
411