aboutsummaryrefslogtreecommitdiff
path: root/crates/ide_assists/src/handlers/remove_unused_param.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/ide_assists/src/handlers/remove_unused_param.rs')
-rw-r--r--crates/ide_assists/src/handlers/remove_unused_param.rs288
1 files changed, 288 insertions, 0 deletions
diff --git a/crates/ide_assists/src/handlers/remove_unused_param.rs b/crates/ide_assists/src/handlers/remove_unused_param.rs
new file mode 100644
index 000000000..c961680e2
--- /dev/null
+++ b/crates/ide_assists/src/handlers/remove_unused_param.rs
@@ -0,0 +1,288 @@
1use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
2use syntax::{
3 algo::find_node_at_range,
4 ast::{self, ArgListOwner},
5 AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
6};
7use test_utils::mark;
8use SyntaxKind::WHITESPACE;
9
10use 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// ```
33pub(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 param_position = func.param_list()?.params().position(|it| it == param)?;
41
42 let fn_def = {
43 let func = ctx.sema.to_def(&func)?;
44 Definition::ModuleDef(func.into())
45 };
46
47 let param_def = {
48 let local = ctx.sema.to_def(&ident_pat)?;
49 Definition::Local(local)
50 };
51 if param_def.usages(&ctx.sema).at_least_one() {
52 mark::hit!(keep_used);
53 return None;
54 }
55 acc.add(
56 AssistId("remove_unused_param", AssistKind::Refactor),
57 "Remove unused parameter",
58 param.syntax().text_range(),
59 |builder| {
60 builder.delete(range_to_remove(param.syntax()));
61 for (file_id, references) in fn_def.usages(&ctx.sema).all() {
62 process_usages(ctx, builder, file_id, references, param_position);
63 }
64 },
65 )
66}
67
68fn process_usages(
69 ctx: &AssistContext,
70 builder: &mut AssistBuilder,
71 file_id: FileId,
72 references: Vec<FileReference>,
73 arg_to_remove: usize,
74) {
75 let source_file = ctx.sema.parse(file_id);
76 builder.edit_file(file_id);
77 for usage in references {
78 if let Some(text_range) = process_usage(&source_file, usage, arg_to_remove) {
79 builder.delete(text_range);
80 }
81 }
82}
83
84fn process_usage(
85 source_file: &SourceFile,
86 FileReference { range, .. }: FileReference,
87 arg_to_remove: usize,
88) -> Option<TextRange> {
89 let call_expr: ast::CallExpr = find_node_at_range(source_file.syntax(), range)?;
90 let call_expr_range = call_expr.expr()?.syntax().text_range();
91 if !call_expr_range.contains_range(range) {
92 return None;
93 }
94 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
95 Some(range_to_remove(arg.syntax()))
96}
97
98fn range_to_remove(node: &SyntaxNode) -> TextRange {
99 let up_to_comma = next_prev().find_map(|dir| {
100 node.siblings_with_tokens(dir)
101 .filter_map(|it| it.into_token())
102 .find(|it| it.kind() == T![,])
103 .map(|it| (dir, it))
104 });
105 if let Some((dir, token)) = up_to_comma {
106 if node.next_sibling().is_some() {
107 let up_to_space = token
108 .siblings_with_tokens(dir)
109 .skip(1)
110 .take_while(|it| it.kind() == WHITESPACE)
111 .last()
112 .and_then(|it| it.into_token());
113 return node
114 .text_range()
115 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
116 }
117 node.text_range().cover(token.text_range())
118 } else {
119 node.text_range()
120 }
121}
122
123#[cfg(test)]
124mod tests {
125 use crate::tests::{check_assist, check_assist_not_applicable};
126
127 use super::*;
128
129 #[test]
130 fn remove_unused() {
131 check_assist(
132 remove_unused_param,
133 r#"
134fn a() { foo(9, 2) }
135fn foo(x: i32, $0y: i32) { x; }
136fn b() { foo(9, 2,) }
137"#,
138 r#"
139fn a() { foo(9) }
140fn foo(x: i32) { x; }
141fn b() { foo(9, ) }
142"#,
143 );
144 }
145
146 #[test]
147 fn remove_unused_first_param() {
148 check_assist(
149 remove_unused_param,
150 r#"
151fn foo($0x: i32, y: i32) { y; }
152fn a() { foo(1, 2) }
153fn b() { foo(1, 2,) }
154"#,
155 r#"
156fn foo(y: i32) { y; }
157fn a() { foo(2) }
158fn b() { foo(2,) }
159"#,
160 );
161 }
162
163 #[test]
164 fn remove_unused_single_param() {
165 check_assist(
166 remove_unused_param,
167 r#"
168fn foo($0x: i32) { 0; }
169fn a() { foo(1) }
170fn b() { foo(1, ) }
171"#,
172 r#"
173fn foo() { 0; }
174fn a() { foo() }
175fn b() { foo( ) }
176"#,
177 );
178 }
179
180 #[test]
181 fn remove_unused_surrounded_by_parms() {
182 check_assist(
183 remove_unused_param,
184 r#"
185fn foo(x: i32, $0y: i32, z: i32) { x; }
186fn a() { foo(1, 2, 3) }
187fn b() { foo(1, 2, 3,) }
188"#,
189 r#"
190fn foo(x: i32, z: i32) { x; }
191fn a() { foo(1, 3) }
192fn b() { foo(1, 3,) }
193"#,
194 );
195 }
196
197 #[test]
198 fn remove_unused_qualified_call() {
199 check_assist(
200 remove_unused_param,
201 r#"
202mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
203fn b() { bar::foo(9, 2) }
204"#,
205 r#"
206mod bar { pub fn foo(x: i32) { x; } }
207fn b() { bar::foo(9) }
208"#,
209 );
210 }
211
212 #[test]
213 fn remove_unused_turbofished_func() {
214 check_assist(
215 remove_unused_param,
216 r#"
217pub fn foo<T>(x: T, $0y: i32) { x; }
218fn b() { foo::<i32>(9, 2) }
219"#,
220 r#"
221pub fn foo<T>(x: T) { x; }
222fn b() { foo::<i32>(9) }
223"#,
224 );
225 }
226
227 #[test]
228 fn remove_unused_generic_unused_param_func() {
229 check_assist(
230 remove_unused_param,
231 r#"
232pub fn foo<T>(x: i32, $0y: T) { x; }
233fn b() { foo::<i32>(9, 2) }
234fn b2() { foo(9, 2) }
235"#,
236 r#"
237pub fn foo<T>(x: i32) { x; }
238fn b() { foo::<i32>(9) }
239fn b2() { foo(9) }
240"#,
241 );
242 }
243
244 #[test]
245 fn keep_used() {
246 mark::check!(keep_used);
247 check_assist_not_applicable(
248 remove_unused_param,
249 r#"
250fn foo(x: i32, $0y: i32) { y; }
251fn main() { foo(9, 2) }
252"#,
253 );
254 }
255
256 #[test]
257 fn remove_across_files() {
258 check_assist(
259 remove_unused_param,
260 r#"
261//- /main.rs
262fn foo(x: i32, $0y: i32) { x; }
263
264mod foo;
265
266//- /foo.rs
267use super::foo;
268
269fn bar() {
270 let _ = foo(1, 2);
271}
272"#,
273 r#"
274//- /main.rs
275fn foo(x: i32) { x; }
276
277mod foo;
278
279//- /foo.rs
280use super::foo;
281
282fn bar() {
283 let _ = foo(1);
284}
285"#,
286 )
287 }
288}