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