1 use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
3 algo::find_node_at_range,
4 ast::{self, HasArgList},
5 AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
8 use SyntaxKind::WHITESPACE;
11 assist_context::SourceChangeBuilder, utils::next_prev, AssistContext, AssistId, AssistKind,
15 // Assist: remove_unused_param
17 // Removes unused function parameter.
20 // fn frobnicate(x: i32$0) {}
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,
40 let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
42 param.syntax().parent()?.children().find_map(ast::SelfParam::cast).is_some();
44 // check if fn is in impl Trait for ..
47 .parent() // AssocItemList
48 .and_then(|x| x.parent())
49 .and_then(ast::Impl::cast)
50 .map_or(false, |imp| imp.trait_().is_some())
52 cov_mark::hit!(trait_impl);
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.
65 let func = ctx.sema.to_def(&func)?;
66 Definition::Function(func)
70 let local = ctx.sema.to_def(&ident_pat)?;
71 Definition::Local(local)
73 if param_def.usages(&ctx.sema).at_least_one() {
74 cov_mark::hit!(keep_used);
78 AssistId("remove_unused_param", AssistKind::Refactor),
79 "Remove unused parameter",
80 param.syntax().text_range(),
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);
91 ctx: &AssistContext<'_>,
92 builder: &mut SourceChangeBuilder,
94 references: Vec<FileReference>,
96 is_self_present: bool,
98 let source_file = ctx.sema.parse(file_id);
99 builder.edit_file(file_id);
100 let possible_ranges = references
102 .filter_map(|usage| process_usage(&source_file, usage, arg_to_remove, is_self_present));
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)
111 for range in ranges_to_delete {
112 builder.delete(range)
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) {
129 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
130 return Some(range_to_remove(arg.syntax()));
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) {
145 let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?;
146 return Some(range_to_remove(arg.syntax()));
152 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![,])
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)
164 .take_while(|it| it.kind() == WHITESPACE)
166 .and_then(|it| it.into_token());
169 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
171 node.text_range().cover(token.text_range())
179 use crate::tests::{check_assist, check_assist_not_applicable};
189 fn foo(x: i32, $0y: i32) { x; }
190 fn b() { foo(9, 2,) }
194 fn foo(x: i32) { x; }
201 fn remove_unused_first_param() {
205 fn foo($0x: i32, y: i32) { y; }
207 fn b() { foo(1, 2,) }
210 fn foo(y: i32) { y; }
218 fn remove_unused_single_param() {
222 fn foo($0x: i32) { 0; }
235 fn remove_unused_surrounded_by_parms() {
239 fn foo(x: i32, $0y: i32, z: i32) { x; }
240 fn a() { foo(1, 2, 3) }
241 fn b() { foo(1, 2, 3,) }
244 fn foo(x: i32, z: i32) { x; }
246 fn b() { foo(1, 3,) }
252 fn remove_unused_qualified_call() {
256 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
257 fn b() { bar::foo(9, 2) }
260 mod bar { pub fn foo(x: i32) { x; } }
261 fn b() { bar::foo(9) }
267 fn remove_unused_turbofished_func() {
271 pub fn foo<T>(x: T, $0y: i32) { x; }
272 fn b() { foo::<i32>(9, 2) }
275 pub fn foo<T>(x: T) { x; }
276 fn b() { foo::<i32>(9) }
282 fn remove_unused_generic_unused_param_func() {
286 pub fn foo<T>(x: i32, $0y: T) { x; }
287 fn b() { foo::<i32>(9, 2) }
288 fn b2() { foo(9, 2) }
291 pub fn foo<T>(x: i32) { x; }
292 fn b() { foo::<i32>(9) }
300 cov_mark::check!(keep_used);
301 check_assist_not_applicable(
304 fn foo(x: i32, $0y: i32) { y; }
305 fn main() { foo(9, 2) }
312 cov_mark::check!(trait_impl);
313 check_assist_not_applicable(
327 fn remove_across_files() {
332 fn foo(x: i32, $0y: i32) { x; }
345 fn foo(x: i32) { x; }
360 fn test_remove_method_param() {
365 impl S { fn f(&self, $0_unused: i32) {} }
375 impl S { fn f(&self) {} }
391 fn foo(x: i32, $0y: i32) -> i32 {
400 fn foo(x: i32) -> i32 {