]> git.lizzy.rs Git - rust.git/blob - crates/ide_assists/src/handlers/remove_unused_param.rs
Merge #7759
[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 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 }