diff options
Diffstat (limited to 'crates/ide_assists/src/handlers/remove_unused_param.rs')
-rw-r--r-- | crates/ide_assists/src/handlers/remove_unused_param.rs | 288 |
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 @@ | |||
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 | use test_utils::mark; | ||
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 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 | |||
68 | fn 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 | |||
84 | fn 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 | |||
98 | fn 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)] | ||
124 | mod 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#" | ||
134 | fn a() { foo(9, 2) } | ||
135 | fn foo(x: i32, $0y: i32) { x; } | ||
136 | fn b() { foo(9, 2,) } | ||
137 | "#, | ||
138 | r#" | ||
139 | fn a() { foo(9) } | ||
140 | fn foo(x: i32) { x; } | ||
141 | fn b() { foo(9, ) } | ||
142 | "#, | ||
143 | ); | ||
144 | } | ||
145 | |||
146 | #[test] | ||
147 | fn remove_unused_first_param() { | ||
148 | check_assist( | ||
149 | remove_unused_param, | ||
150 | r#" | ||
151 | fn foo($0x: i32, y: i32) { y; } | ||
152 | fn a() { foo(1, 2) } | ||
153 | fn b() { foo(1, 2,) } | ||
154 | "#, | ||
155 | r#" | ||
156 | fn foo(y: i32) { y; } | ||
157 | fn a() { foo(2) } | ||
158 | fn b() { foo(2,) } | ||
159 | "#, | ||
160 | ); | ||
161 | } | ||
162 | |||
163 | #[test] | ||
164 | fn remove_unused_single_param() { | ||
165 | check_assist( | ||
166 | remove_unused_param, | ||
167 | r#" | ||
168 | fn foo($0x: i32) { 0; } | ||
169 | fn a() { foo(1) } | ||
170 | fn b() { foo(1, ) } | ||
171 | "#, | ||
172 | r#" | ||
173 | fn foo() { 0; } | ||
174 | fn a() { foo() } | ||
175 | fn b() { foo( ) } | ||
176 | "#, | ||
177 | ); | ||
178 | } | ||
179 | |||
180 | #[test] | ||
181 | fn remove_unused_surrounded_by_parms() { | ||
182 | check_assist( | ||
183 | remove_unused_param, | ||
184 | r#" | ||
185 | fn foo(x: i32, $0y: i32, z: i32) { x; } | ||
186 | fn a() { foo(1, 2, 3) } | ||
187 | fn b() { foo(1, 2, 3,) } | ||
188 | "#, | ||
189 | r#" | ||
190 | fn foo(x: i32, z: i32) { x; } | ||
191 | fn a() { foo(1, 3) } | ||
192 | fn 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#" | ||
202 | mod bar { pub fn foo(x: i32, $0y: i32) { x; } } | ||
203 | fn b() { bar::foo(9, 2) } | ||
204 | "#, | ||
205 | r#" | ||
206 | mod bar { pub fn foo(x: i32) { x; } } | ||
207 | fn 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#" | ||
217 | pub fn foo<T>(x: T, $0y: i32) { x; } | ||
218 | fn b() { foo::<i32>(9, 2) } | ||
219 | "#, | ||
220 | r#" | ||
221 | pub fn foo<T>(x: T) { x; } | ||
222 | fn 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#" | ||
232 | pub fn foo<T>(x: i32, $0y: T) { x; } | ||
233 | fn b() { foo::<i32>(9, 2) } | ||
234 | fn b2() { foo(9, 2) } | ||
235 | "#, | ||
236 | r#" | ||
237 | pub fn foo<T>(x: i32) { x; } | ||
238 | fn b() { foo::<i32>(9) } | ||
239 | fn 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#" | ||
250 | fn foo(x: i32, $0y: i32) { y; } | ||
251 | fn 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 | ||
262 | fn foo(x: i32, $0y: i32) { x; } | ||
263 | |||
264 | mod foo; | ||
265 | |||
266 | //- /foo.rs | ||
267 | use super::foo; | ||
268 | |||
269 | fn bar() { | ||
270 | let _ = foo(1, 2); | ||
271 | } | ||
272 | "#, | ||
273 | r#" | ||
274 | //- /main.rs | ||
275 | fn foo(x: i32) { x; } | ||
276 | |||
277 | mod foo; | ||
278 | |||
279 | //- /foo.rs | ||
280 | use super::foo; | ||
281 | |||
282 | fn bar() { | ||
283 | let _ = foo(1); | ||
284 | } | ||
285 | "#, | ||
286 | ) | ||
287 | } | ||
288 | } | ||