1 use ide_db::{base_db::FileId, defs::Definition, search::FileReference};
3 algo::find_node_at_range,
4 ast::{self, ArgListOwner},
5 AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T,
8 use SyntaxKind::WHITESPACE;
11 assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
14 // Assist: remove_unused_param
16 // Removes unused function parameter.
19 // fn frobnicate(x: i32$0) {}
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,
39 let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
40 let param_position = func.param_list()?.params().position(|it| it == param)?;
43 let func = ctx.sema.to_def(&func)?;
44 Definition::ModuleDef(func.into())
48 let local = ctx.sema.to_def(&ident_pat)?;
49 Definition::Local(local)
51 if param_def.usages(&ctx.sema).at_least_one() {
52 mark::hit!(keep_used);
56 AssistId("remove_unused_param", AssistKind::Refactor),
57 "Remove unused parameter",
58 param.syntax().text_range(),
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);
70 builder: &mut AssistBuilder,
72 references: Vec<FileReference>,
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);
85 source_file: &SourceFile,
86 FileReference { range, .. }: FileReference,
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) {
94 let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
95 Some(range_to_remove(arg.syntax()))
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![,])
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)
110 .take_while(|it| it.kind() == WHITESPACE)
112 .and_then(|it| it.into_token());
115 .cover(up_to_space.map_or(token.text_range(), |it| it.text_range()));
117 node.text_range().cover(token.text_range())
125 use crate::tests::{check_assist, check_assist_not_applicable};
135 fn foo(x: i32, $0y: i32) { x; }
136 fn b() { foo(9, 2,) }
140 fn foo(x: i32) { x; }
147 fn remove_unused_first_param() {
151 fn foo($0x: i32, y: i32) { y; }
153 fn b() { foo(1, 2,) }
156 fn foo(y: i32) { y; }
164 fn remove_unused_single_param() {
168 fn foo($0x: i32) { 0; }
181 fn remove_unused_surrounded_by_parms() {
185 fn foo(x: i32, $0y: i32, z: i32) { x; }
186 fn a() { foo(1, 2, 3) }
187 fn b() { foo(1, 2, 3,) }
190 fn foo(x: i32, z: i32) { x; }
192 fn b() { foo(1, 3,) }
198 fn remove_unused_qualified_call() {
202 mod bar { pub fn foo(x: i32, $0y: i32) { x; } }
203 fn b() { bar::foo(9, 2) }
206 mod bar { pub fn foo(x: i32) { x; } }
207 fn b() { bar::foo(9) }
213 fn remove_unused_turbofished_func() {
217 pub fn foo<T>(x: T, $0y: i32) { x; }
218 fn b() { foo::<i32>(9, 2) }
221 pub fn foo<T>(x: T) { x; }
222 fn b() { foo::<i32>(9) }
228 fn remove_unused_generic_unused_param_func() {
232 pub fn foo<T>(x: i32, $0y: T) { x; }
233 fn b() { foo::<i32>(9, 2) }
234 fn b2() { foo(9, 2) }
237 pub fn foo<T>(x: i32) { x; }
238 fn b() { foo::<i32>(9) }
246 mark::check!(keep_used);
247 check_assist_not_applicable(
250 fn foo(x: i32, $0y: i32) { y; }
251 fn main() { foo(9, 2) }
257 fn remove_across_files() {
262 fn foo(x: i32, $0y: i32) { x; }
275 fn foo(x: i32) { x; }