diff --git a/crates/ide-assists/src/handlers/remove_unused_param.rs b/crates/ide-assists/src/handlers/remove_unused_param.rs index 75120768da..5ddb17b207 100644 --- a/crates/ide-assists/src/handlers/remove_unused_param.rs +++ b/crates/ide-assists/src/handlers/remove_unused_param.rs @@ -1,8 +1,9 @@ use ide_db::{defs::Definition, search::FileReference, EditionedFileId}; use syntax::{ - algo::find_node_at_range, + algo::{find_node_at_range, least_common_ancestor_element}, ast::{self, HasArgList}, - AstNode, SourceFile, SyntaxKind, SyntaxNode, TextRange, T, + syntax_editor::Element, + AstNode, SourceFile, SyntaxElement, SyntaxKind, SyntaxNode, TextRange, T, }; use SyntaxKind::WHITESPACE; @@ -74,15 +75,21 @@ pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext<'_>) -> cov_mark::hit!(keep_used); return None; } + let parent = param.syntax().parent()?; acc.add( AssistId("remove_unused_param", AssistKind::Refactor), "Remove unused parameter", param.syntax().text_range(), |builder| { - builder.delete(range_to_remove(param.syntax())); + let mut editor = builder.make_editor(&parent); + let elements = elements_to_remove(param.syntax()); + for element in elements { + editor.delete(element); + } for (file_id, references) in fn_def.usages(&ctx.sema).all() { process_usages(ctx, builder, file_id, references, param_position, is_self_present); } + builder.add_file_edits(ctx.file_id(), editor); }, ) } @@ -96,20 +103,24 @@ fn process_usages( is_self_present: bool, ) { let source_file = ctx.sema.parse(file_id); - builder.edit_file(file_id); let possible_ranges = references .into_iter() .filter_map(|usage| process_usage(&source_file, usage, arg_to_remove, is_self_present)); - let mut ranges_to_delete: Vec = vec![]; - for range in possible_ranges { - if !ranges_to_delete.iter().any(|it| it.contains_range(range)) { - ranges_to_delete.push(range) + for element_range in possible_ranges { + let Some(SyntaxElement::Node(parent)) = element_range + .iter() + .cloned() + .reduce(|a, b| least_common_ancestor_element(&a, &b).unwrap().syntax_element()) + else { + continue; + }; + let mut editor = builder.make_editor(&parent); + for element in element_range { + editor.delete(element); } - } - for range in ranges_to_delete { - builder.delete(range) + builder.add_file_edits(file_id, editor); } } @@ -118,7 +129,7 @@ fn process_usage( FileReference { range, .. }: FileReference, mut arg_to_remove: usize, is_self_present: bool, -) -> Option { +) -> Option> { let call_expr_opt: Option = find_node_at_range(source_file.syntax(), range); if let Some(call_expr) = call_expr_opt { let call_expr_range = call_expr.expr()?.syntax().text_range(); @@ -127,7 +138,7 @@ fn process_usage( } let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?; - return Some(range_to_remove(arg.syntax())); + return Some(elements_to_remove(arg.syntax())); } let method_call_expr_opt: Option = @@ -143,7 +154,7 @@ fn process_usage( } let arg = method_call_expr.arg_list()?.args().nth(arg_to_remove)?; - return Some(range_to_remove(arg.syntax())); + return Some(elements_to_remove(arg.syntax())); } None @@ -174,6 +185,29 @@ pub(crate) fn range_to_remove(node: &SyntaxNode) -> TextRange { } } +pub(crate) fn elements_to_remove(node: &SyntaxNode) -> Vec { + let up_to_comma = next_prev().find_map(|dir| { + node.siblings_with_tokens(dir) + .filter_map(|it| it.into_token()) + .find(|it| it.kind() == T![,]) + .map(|it| (dir, it)) + }); + if let Some((dir, token)) = up_to_comma { + let after = token.siblings_with_tokens(dir).nth(1).unwrap(); + let mut result: Vec<_> = + node.siblings_with_tokens(dir).take_while(|it| it != &after).collect(); + if node.next_sibling().is_some() { + result.extend( + token.siblings_with_tokens(dir).skip(1).take_while(|it| it.kind() == WHITESPACE), + ); + } + + result + } else { + vec![node.syntax_element()] + } +} + #[cfg(test)] mod tests { use crate::tests::{check_assist, check_assist_not_applicable}; diff --git a/crates/syntax/src/algo.rs b/crates/syntax/src/algo.rs index 2acb215831..3b85b137aa 100644 --- a/crates/syntax/src/algo.rs +++ b/crates/syntax/src/algo.rs @@ -3,8 +3,8 @@ use itertools::Itertools; use crate::{ - AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, SyntaxToken, TextRange, - TextSize, + syntax_editor::Element, AstNode, Direction, NodeOrToken, SyntaxElement, SyntaxKind, SyntaxNode, + SyntaxToken, TextRange, TextSize, }; /// Returns ancestors of the node at the offset, sorted by length. This should @@ -89,6 +89,26 @@ pub fn least_common_ancestor(u: &SyntaxNode, v: &SyntaxNode) -> Option Option { + let u = u.syntax_element(); + let v = v.syntax_element(); + if u == v { + return match u { + NodeOrToken::Node(node) => Some(node), + NodeOrToken::Token(token) => token.parent(), + }; + } + + let u_depth = u.ancestors().count(); + let v_depth = v.ancestors().count(); + let keep = u_depth.min(v_depth); + + let u_candidates = u.ancestors().skip(u_depth - keep); + let v_candidates = v.ancestors().skip(v_depth - keep); + let (res, _) = u_candidates.zip(v_candidates).find(|(x, y)| x == y)?; + Some(res) +} + pub fn neighbor(me: &T, direction: Direction) -> Option { me.syntax().siblings(direction).skip(1).find_map(T::cast) } diff --git a/docs/book/src/assists_generated.md b/docs/book/src/assists_generated.md index a05536e2d2..2d233ca62a 100644 --- a/docs/book/src/assists_generated.md +++ b/docs/book/src/assists_generated.md @@ -3015,7 +3015,7 @@ mod foo { ### `remove_unused_param` -**Source:** [remove_unused_param.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/remove_unused_param.rs#L15) +**Source:** [remove_unused_param.rs](https://github.com/rust-lang/rust-analyzer/blob/master/crates/ide-assists/src/handlers/remove_unused_param.rs#L16) Removes unused function parameter.