Repository: rust-analyzer/rust-analyzer
Pull request #5821 (2020-08-19 17:49:20)

**Remove Unused Parameter** refactoring

bors r+
🤖


Patch:
diff --git a/crates/assists/src/handlers/merge_imports.rs b/crates/assists/src/handlers/merge_imports.rs
index 47d4654046..35b884206f 100644
--- a/crates/assists/src/handlers/merge_imports.rs
+++ b/crates/assists/src/handlers/merge_imports.rs
@@ -8,6 +8,7 @@ use syntax::{
 
 use crate::{
     assist_context::{AssistContext, Assists},
+    utils::next_prev,
     AssistId, AssistKind,
 };
 
@@ -66,10 +67,6 @@ pub(crate) fn merge_imports(acc: &mut Assists, ctx: &AssistContext) -> Option<()
     )
 }
 
-fn next_prev() -> impl Iterator<Item = Direction> {
-    [Direction::Next, Direction::Prev].iter().copied()
-}
-
 fn try_merge_trees(old: &ast::UseTree, new: &ast::UseTree) -> Option<ast::UseTree> {
     let lhs_path = old.path()?;
     let rhs_path = new.path()?;
diff --git a/crates/assists/src/handlers/remove_dbg.rs b/crates/assists/src/handlers/remove_dbg.rs
index f3dcca5348..4e252edf02 100644
--- a/crates/assists/src/handlers/remove_dbg.rs
+++ b/crates/assists/src/handlers/remove_dbg.rs
@@ -82,9 +82,10 @@ fn is_valid_macrocall(macro_call: &ast::MacroCall, macro_name: &str) -> Option<b
 
 #[cfg(test)]
 mod tests {
-    use super::*;
     use crate::tests::{check_assist, check_assist_not_applicable, check_assist_target};
 
+    use super::*;
+
     #[test]
     fn test_remove_dbg() {
         check_assist(remove_dbg, "<|>dbg!(1 + 1)", "1 + 1");
diff --git a/crates/assists/src/handlers/remove_unused_param.rs b/crates/assists/src/handlers/remove_unused_param.rs
new file mode 100644
index 0000000000..5fccca54b8
--- /dev/null
+++ b/crates/assists/src/handlers/remove_unused_param.rs
@@ -0,0 +1,131 @@
+use ide_db::{defs::Definition, search::Reference};
+use syntax::{
+    algo::find_node_at_range,
+    ast::{self, ArgListOwner},
+    AstNode, SyntaxNode, TextRange, T,
+};
+use test_utils::mark;
+
+use crate::{
+    assist_context::AssistBuilder, utils::next_prev, AssistContext, AssistId, AssistKind, Assists,
+};
+
+// Assist: remove_unused_param
+//
+// Removes unused function parameter.
+//
+// ```
+// fn frobnicate(x: i32<|>) {}
+//
+// fn main() {
+//     frobnicate(92);
+// }
+// ```
+// ->
+// ```
+// fn frobnicate() {}
+//
+// fn main() {
+//     frobnicate();
+// }
+// ```
+pub(crate) fn remove_unused_param(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
+    let param: ast::Param = ctx.find_node_at_offset()?;
+    let ident_pat = match param.pat()? {
+        ast::Pat::IdentPat(it) => it,
+        _ => return None,
+    };
+    let func = param.syntax().ancestors().find_map(ast::Fn::cast)?;
+    let param_position = func.param_list()?.params().position(|it| it == param)?;
+
+    let fn_def = {
+        let func = ctx.sema.to_def(&func)?;
+        Definition::ModuleDef(func.into())
+    };
+
+    let param_def = {
+        let local = ctx.sema.to_def(&ident_pat)?;
+        Definition::Local(local)
+    };
+    if param_def.usages(&ctx.sema).at_least_one() {
+        mark::hit!(keep_used);
+        return None;
+    }
+    acc.add(
+        AssistId("remove_unused_param", AssistKind::Refactor),
+        "Remove unused parameter",
+        param.syntax().text_range(),
+        |builder| {
+            builder.delete(range_with_coma(param.syntax()));
+            for usage in fn_def.usages(&ctx.sema).all() {
+                process_usage(ctx, builder, usage, param_position);
+            }
+        },
+    )
+}
+
+fn process_usage(
+    ctx: &AssistContext,
+    builder: &mut AssistBuilder,
+    usage: Reference,
+    arg_to_remove: usize,
+) -> Option<()> {
+    let source_file = ctx.sema.parse(usage.file_range.file_id);
+    let call_expr: ast::CallExpr =
+        find_node_at_range(source_file.syntax(), usage.file_range.range)?;
+    if call_expr.expr()?.syntax().text_range() != usage.file_range.range {
+        return None;
+    }
+    let arg = call_expr.arg_list()?.args().nth(arg_to_remove)?;
+
+    builder.edit_file(usage.file_range.file_id);
+    builder.delete(range_with_coma(arg.syntax()));
+
+    Some(())
+}
+
+fn range_with_coma(node: &SyntaxNode) -> TextRange {
+    let up_to = next_prev().find_map(|dir| {
+        node.siblings_with_tokens(dir)
+            .filter_map(|it| it.into_token())
+            .find(|it| it.kind() == T![,])
+    });
+    let up_to = up_to.map_or(node.text_range(), |it| it.text_range());
+    node.text_range().cover(up_to)
+}
+
+#[cfg(test)]
+mod tests {
+    use crate::tests::{check_assist, check_assist_not_applicable};
+
+    use super::*;
+
+    #[test]
+    fn remove_unused() {
+        check_assist(
+            remove_unused_param,
+            r#"
+fn a() { foo(9, 2) }
+fn foo(x: i32, <|>y: i32) { x; }
+fn b() { foo(9, 2,) }
+"#,
+            r#"
+fn a() { foo(9) }
+fn foo(x: i32) { x; }
+fn b() { foo(9, ) }
+"#,
+        );
+    }
+
+    #[test]
+    fn keep_used() {
+        mark::check!(keep_used);
+        check_assist_not_applicable(
+            remove_unused_param,
+            r#"
+fn foo(x: i32, <|>y: i32) { y; }
+fn main() { foo(9, 2) }
+"#,
+        );
+    }
+}
diff --git a/crates/assists/src/lib.rs b/crates/assists/src/lib.rs
index 14834480ac..2e0d191a60 100644
--- a/crates/assists/src/lib.rs
+++ b/crates/assists/src/lib.rs
@@ -152,6 +152,7 @@ mod handlers {
     mod raw_string;
     mod remove_dbg;
     mod remove_mut;
+    mod remove_unused_param;
     mod reorder_fields;
     mod replace_if_let_with_match;
     mod replace_let_with_if_let;
@@ -198,6 +199,7 @@ mod handlers {
             raw_string::remove_hash,
             remove_dbg::remove_dbg,
             remove_mut::remove_mut,
+            remove_unused_param::remove_unused_param,
             reorder_fields::reorder_fields,
             replace_if_let_with_match::replace_if_let_with_match,
             replace_let_with_if_let::replace_let_with_if_let,
diff --git a/crates/assists/src/utils.rs b/crates/assists/src/utils.rs
index 84ccacafe3..d071d6502f 100644
--- a/crates/assists/src/utils.rs
+++ b/crates/assists/src/utils.rs
@@ -9,7 +9,7 @@ use itertools::Itertools;
 use rustc_hash::FxHashSet;
 use syntax::{
     ast::{self, make, NameOwner},
-    AstNode,
+    AstNode, Direction,
     SyntaxKind::*,
     SyntaxNode, TextSize, T,
 };
@@ -311,3 +311,7 @@ pub use prelude::*;
         Some(def)
     }
 }
+
+pub(crate) fn next_prev() -> impl Iterator<Item = Direction> {
+    [Direction::Next, Direction::Prev].iter().copied()
+}
diff --git a/crates/ide_db/src/search.rs b/crates/ide_db/src/search.rs
index ce7631c692..fa0830b236 100644
--- a/crates/ide_db/src/search.rs
+++ b/crates/ide_db/src/search.rs
@@ -203,7 +203,7 @@ impl<'a> FindUsages<'a> {
     }
 
     pub fn at_least_one(self) -> bool {
-        self.all().is_empty()
+        !self.all().is_empty()
     }
 
     pub fn all(self) -> Vec<Reference> {


Test patch:
diff --git a/crates/assists/src/tests/generated.rs b/crates/assists/src/tests/generated.rs
index 1735670037..04c8fd1f94 100644
--- a/crates/assists/src/tests/generated.rs
+++ b/crates/assists/src/tests/generated.rs
@@ -750,6 +750,27 @@ impl Walrus {
     )
 }
 
+#[test]
+fn doctest_remove_unused_param() {
+    check_doc_test(
+        "remove_unused_param",
+        r#####"
+fn frobnicate(x: i32<|>) {}
+
+fn main() {
+    frobnicate(92);
+}
+"#####,
+        r#####"
+fn frobnicate() {}
+
+fn main() {
+    frobnicate();
+}
+"#####,
+    )
+}
+
 #[test]
 fn doctest_reorder_fields() {
     check_doc_test(