Unnamed repository; edit this file 'description' to name the repository.
Auto merge of #14723 - obsgolem:master, r=Veykril
Added remove unused imports assist This resolves the most important part of #5131. I needed to make a couple of cosmetic changes to the search infrastructure to do this. A few open questions: * Should imports that don't resolve to anything be considered unused? I figured probably not, but it would be a trivial change to make if we want it. * Is there a cleaner way to make the edits to the use list? * Is there a cleaner way to get the list of uses that intersect the current selection? * Is the performance acceptable? When testing this on itself, it takes a good couple seconds to perform the assist. * Is there a way to hide the rustc diagnostics that overlap with this functionality?
bors 2023-08-01
parent 43ecf29 · parent 7a87a35 · commit 62dcf39
-rw-r--r--crates/ide-assists/src/handlers/destructure_tuple_binding.rs2
-rw-r--r--crates/ide-assists/src/handlers/expand_glob_import.rs2
-rw-r--r--crates/ide-assists/src/handlers/extract_function.rs2
-rw-r--r--crates/ide-assists/src/handlers/extract_module.rs2
-rw-r--r--crates/ide-assists/src/handlers/inline_call.rs2
-rw-r--r--crates/ide-assists/src/handlers/move_const_to_impl.rs16
-rw-r--r--crates/ide-assists/src/handlers/remove_unused_imports.rs739
-rw-r--r--crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs2
-rw-r--r--crates/ide-assists/src/lib.rs2
-rw-r--r--crates/ide-assists/src/tests/generated.rs18
-rw-r--r--crates/ide-db/src/search.rs12
-rw-r--r--crates/ide-ssr/src/search.rs2
-rw-r--r--crates/ide/src/highlight_related.rs9
-rw-r--r--crates/ide/src/references.rs2
-rw-r--r--crates/ide/src/runnables.rs2
-rw-r--r--crates/syntax/src/ast/edit_in_place.rs36
16 files changed, 822 insertions, 28 deletions
diff --git a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
index ea71d165e6..f30ca2552d 100644
--- a/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
+++ b/crates/ide-assists/src/handlers/destructure_tuple_binding.rs
@@ -114,7 +114,7 @@ fn collect_data(ident_pat: IdentPat, ctx: &AssistContext<'_>) -> Option<TupleDat
let usages = ctx.sema.to_def(&ident_pat).map(|def| {
Definition::Local(def)
.usages(&ctx.sema)
- .in_scope(SearchScope::single_file(ctx.file_id()))
+ .in_scope(&SearchScope::single_file(ctx.file_id()))
.all()
});
diff --git a/crates/ide-assists/src/handlers/expand_glob_import.rs b/crates/ide-assists/src/handlers/expand_glob_import.rs
index 3aff5c9144..9beb616d99 100644
--- a/crates/ide-assists/src/handlers/expand_glob_import.rs
+++ b/crates/ide-assists/src/handlers/expand_glob_import.rs
@@ -120,7 +120,7 @@ fn find_parent_and_path(
fn def_is_referenced_in(def: Definition, ctx: &AssistContext<'_>) -> bool {
let search_scope = SearchScope::single_file(ctx.file_id());
- def.usages(&ctx.sema).in_scope(search_scope).at_least_one()
+ def.usages(&ctx.sema).in_scope(&search_scope).at_least_one()
}
#[derive(Debug, Clone)]
diff --git a/crates/ide-assists/src/handlers/extract_function.rs b/crates/ide-assists/src/handlers/extract_function.rs
index e9db38aca0..b8b781ea48 100644
--- a/crates/ide-assists/src/handlers/extract_function.rs
+++ b/crates/ide-assists/src/handlers/extract_function.rs
@@ -384,7 +384,7 @@ impl LocalUsages {
Self(
Definition::Local(var)
.usages(&ctx.sema)
- .in_scope(SearchScope::single_file(ctx.file_id()))
+ .in_scope(&SearchScope::single_file(ctx.file_id()))
.all(),
)
}
diff --git a/crates/ide-assists/src/handlers/extract_module.rs b/crates/ide-assists/src/handlers/extract_module.rs
index de37f5f130..6839c5820d 100644
--- a/crates/ide-assists/src/handlers/extract_module.rs
+++ b/crates/ide-assists/src/handlers/extract_module.rs
@@ -478,7 +478,7 @@ impl Module {
let selection_range = ctx.selection_trimmed();
let curr_file_id = ctx.file_id();
let search_scope = SearchScope::single_file(curr_file_id);
- let usage_res = def.usages(&ctx.sema).in_scope(search_scope).all();
+ let usage_res = def.usages(&ctx.sema).in_scope(&search_scope).all();
let file = ctx.sema.parse(curr_file_id);
let mut exists_inside_sel = false;
diff --git a/crates/ide-assists/src/handlers/inline_call.rs b/crates/ide-assists/src/handlers/inline_call.rs
index 67036029f5..ffab58509b 100644
--- a/crates/ide-assists/src/handlers/inline_call.rs
+++ b/crates/ide-assists/src/handlers/inline_call.rs
@@ -80,7 +80,7 @@ pub(crate) fn inline_into_callers(acc: &mut Assists, ctx: &AssistContext<'_>) ->
let is_recursive_fn = usages
.clone()
- .in_scope(SearchScope::file_range(FileRange {
+ .in_scope(&SearchScope::file_range(FileRange {
file_id: def_file,
range: func_body.syntax().text_range(),
}))
diff --git a/crates/ide-assists/src/handlers/move_const_to_impl.rs b/crates/ide-assists/src/handlers/move_const_to_impl.rs
index e1849eb71d..22d536b5af 100644
--- a/crates/ide-assists/src/handlers/move_const_to_impl.rs
+++ b/crates/ide-assists/src/handlers/move_const_to_impl.rs
@@ -82,17 +82,19 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
return None;
}
- let usages =
- Definition::Const(def).usages(&ctx.sema).in_scope(SearchScope::file_range(FileRange {
- file_id: ctx.file_id(),
- range: parent_fn.syntax().text_range(),
- }));
-
acc.add(
AssistId("move_const_to_impl", crate::AssistKind::RefactorRewrite),
"Move const to impl block",
const_.syntax().text_range(),
|builder| {
+ let usages = Definition::Const(def)
+ .usages(&ctx.sema)
+ .in_scope(&SearchScope::file_range(FileRange {
+ file_id: ctx.file_id(),
+ range: parent_fn.syntax().text_range(),
+ }))
+ .all();
+
let range_to_delete = match const_.syntax().next_sibling_or_token() {
Some(s) if matches!(s.kind(), SyntaxKind::WHITESPACE) => {
// Remove following whitespaces too.
@@ -103,7 +105,7 @@ pub(crate) fn move_const_to_impl(acc: &mut Assists, ctx: &AssistContext<'_>) ->
builder.delete(range_to_delete);
let const_ref = format!("Self::{}", name.display(ctx.db()));
- for range in usages.all().file_ranges().map(|it| it.range) {
+ for range in usages.file_ranges().map(|it| it.range) {
builder.replace(range, const_ref.clone());
}
diff --git a/crates/ide-assists/src/handlers/remove_unused_imports.rs b/crates/ide-assists/src/handlers/remove_unused_imports.rs
new file mode 100644
index 0000000000..dd4839351f
--- /dev/null
+++ b/crates/ide-assists/src/handlers/remove_unused_imports.rs
@@ -0,0 +1,739 @@
+use std::collections::{hash_map::Entry, HashMap};
+
+use hir::{InFile, Module, ModuleSource};
+use ide_db::{
+ base_db::FileRange,
+ defs::Definition,
+ search::{FileReference, ReferenceCategory, SearchScope},
+ RootDatabase,
+};
+use syntax::{ast, AstNode};
+use text_edit::TextRange;
+
+use crate::{AssistContext, AssistId, AssistKind, Assists};
+
+// Assist: remove_unused_imports
+//
+// Removes any use statements in the current selection that are unused.
+//
+// ```
+// struct X();
+// mod foo {
+// use super::X$0;
+// }
+// ```
+// ->
+// ```
+// struct X();
+// mod foo {
+// }
+// ```
+pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
+ // First, grab the uses that intersect with the current selection.
+ let selected_el = match ctx.covering_element() {
+ syntax::NodeOrToken::Node(n) => n,
+ syntax::NodeOrToken::Token(t) => t.parent()?,
+ };
+
+ // This applies to all uses that are selected, or are ancestors of our selection.
+ let uses_up = selected_el.ancestors().skip(1).filter_map(ast::Use::cast);
+ let uses_down = selected_el
+ .descendants()
+ .filter(|x| x.text_range().intersect(ctx.selection_trimmed()).is_some())
+ .filter_map(ast::Use::cast);
+ let uses = uses_up.chain(uses_down).collect::<Vec<_>>();
+
+ // Maps use nodes to the scope that we should search through to find
+ let mut search_scopes = HashMap::<Module, Vec<SearchScope>>::new();
+
+ // iterator over all unused use trees
+ let mut unused = uses
+ .into_iter()
+ .flat_map(|u| u.syntax().descendants().filter_map(ast::UseTree::cast))
+ .filter(|u| u.use_tree_list().is_none())
+ .filter_map(|u| {
+ // Find any uses trees that are unused
+
+ let use_module = ctx.sema.scope(&u.syntax()).map(|s| s.module())?;
+ let scope = match search_scopes.entry(use_module) {
+ Entry::Occupied(o) => o.into_mut(),
+ Entry::Vacant(v) => v.insert(module_search_scope(ctx.db(), use_module)),
+ };
+
+ // Gets the path associated with this use tree. If there isn't one, then ignore this use tree.
+ let path = if let Some(path) = u.path() {
+ path
+ } else if u.star_token().is_some() {
+ // This case maps to the situation where the * token is braced.
+ // In this case, the parent use tree's path is the one we should use to resolve the glob.
+ match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) {
+ Some(parent_u) if parent_u.path().is_some() => parent_u.path().unwrap(),
+ _ => return None,
+ }
+ } else {
+ return None;
+ };
+
+ // Get the actual definition associated with this use item.
+ let res = match ctx.sema.resolve_path(&path) {
+ Some(x) => x,
+ None => {
+ return None;
+ }
+ };
+
+ let def = match res {
+ hir::PathResolution::Def(d) => Definition::from(d),
+ _ => return None,
+ };
+
+ if u.star_token().is_some() {
+ // Check if any of the children of this module are used
+ let def_mod = match def {
+ Definition::Module(module) => module,
+ _ => return None,
+ };
+
+ if !def_mod
+ .scope(ctx.db(), Some(use_module))
+ .iter()
+ .filter_map(|(_, x)| match x {
+ hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)),
+ _ => None,
+ })
+ .any(|d| used_once_in_scope(ctx, d, scope))
+ {
+ return Some(u);
+ }
+ } else if let Definition::Trait(ref t) = def {
+ // If the trait or any item is used.
+ if !std::iter::once(def)
+ .chain(t.items(ctx.db()).into_iter().map(Definition::from))
+ .any(|d| used_once_in_scope(ctx, d, scope))
+ {
+ return Some(u);
+ }
+ } else {
+ if !used_once_in_scope(ctx, def, &scope) {
+ return Some(u);
+ }
+ }
+
+ None
+ })
+ .peekable();
+
+ // Peek so we terminate early if an unused use is found. Only do the rest of the work if the user selects the assist.
+ if unused.peek().is_some() {
+ acc.add(
+ AssistId("remove_unused_imports", AssistKind::QuickFix),
+ "Remove all the unused imports",
+ selected_el.text_range(),
+ |builder| {
+ let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect();
+ for node in unused {
+ node.remove_recursive();
+ }
+ },
+ )
+ } else {
+ None
+ }
+}
+
+fn used_once_in_scope(ctx: &AssistContext<'_>, def: Definition, scopes: &Vec<SearchScope>) -> bool {
+ let mut found = false;
+
+ for scope in scopes {
+ let mut search_non_import = |_, r: FileReference| {
+ // The import itself is a use; we must skip that.
+ if r.category != Some(ReferenceCategory::Import) {
+ found = true;
+ true
+ } else {
+ false
+ }
+ };
+ def.usages(&ctx.sema).in_scope(scope).search(&mut search_non_import);
+ if found {
+ break;
+ }
+ }
+
+ found
+}
+
+/// Build a search scope spanning the given module but none of its submodules.
+fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec<SearchScope> {
+ let (file_id, range) = {
+ let InFile { file_id, value } = module.definition_source(db);
+ if let Some((file_id, call_source)) = file_id.original_call_node(db) {
+ (file_id, Some(call_source.text_range()))
+ } else {
+ (
+ file_id.original_file(db),
+ match value {
+ ModuleSource::SourceFile(_) => None,
+ ModuleSource::Module(it) => Some(it.syntax().text_range()),
+ ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()),
+ },
+ )
+ }
+ };
+
+ fn split_at_subrange(first: TextRange, second: TextRange) -> (TextRange, Option<TextRange>) {
+ let intersect = first.intersect(second);
+ if let Some(intersect) = intersect {
+ let start_range = TextRange::new(first.start(), intersect.start());
+
+ if intersect.end() < first.end() {
+ (start_range, Some(TextRange::new(intersect.end(), first.end())))
+ } else {
+ (start_range, None)
+ }
+ } else {
+ (first, None)
+ }
+ }
+
+ let mut scopes = Vec::new();
+ if let Some(range) = range {
+ let mut ranges = vec![range];
+
+ for child in module.children(db) {
+ let rng = match child.definition_source(db).value {
+ ModuleSource::SourceFile(_) => continue,
+ ModuleSource::Module(it) => it.syntax().text_range(),
+ ModuleSource::BlockExpr(_) => continue,
+ };
+ let mut new_ranges = Vec::new();
+ for old_range in ranges.iter_mut() {
+ let split = split_at_subrange(old_range.clone(), rng);
+ *old_range = split.0;
+ new_ranges.extend(split.1);
+ }
+
+ ranges.append(&mut new_ranges);
+ }
+
+ for range in ranges {
+ scopes.push(SearchScope::file_range(FileRange { file_id, range }));
+ }
+ } else {
+ scopes.push(SearchScope::single_file(file_id));
+ }
+
+ scopes
+}
+
+#[cfg(test)]
+mod tests {
+ use crate::tests::{check_assist, check_assist_not_applicable};
+
+ use super::*;
+
+ #[test]
+ fn remove_unused() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::X;
+ use super::Y;$0
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_is_precise() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+$0use super::X;$0
+
+fn w() {
+ struct X();
+ let x = X();
+}
+}
+"#,
+ r#"
+struct X();
+mod z {
+
+fn w() {
+ struct X();
+ let x = X();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_name_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f();
+}
+
+impl Y for X {
+ fn f() {}
+}
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ X::f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ranamed_trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y as Z;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn ranamed_underscore_trait_item_use_is_use() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+trait Y {
+ fn f(self);
+}
+
+impl Y for X {
+ fn f(self) {}
+}
+mod z {
+$0use super::X;
+use super::Y as _;$0
+
+fn w() {
+ let x = X();
+ x.f();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn dont_remove_used() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+$0use super::X;
+use super::Y;$0
+
+fn w() {
+ let x = X();
+ let y = Y();
+}
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_in_braces() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::{X, Y};$0
+
+ fn w() {
+ let x = X();
+ }
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::{X};
+
+ fn w() {
+ let x = X();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_under_cursor() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+ use super::X$0;
+}
+"#,
+ r#"
+struct X();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_multi_use_block() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+$0mod y {
+ use super::X;
+}
+mod z {
+ use super::X;
+}$0
+"#,
+ r#"
+struct X();
+mod y {
+}
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X};
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested_first_item() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ fn f() {
+ let y = Y();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{y::Y};
+ fn f() {
+ let y = Y();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_nested_all_unused() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ use crate::{X, y::Y}$0;
+ }
+}
+"#,
+ r#"
+struct X();
+mod y {
+ struct Y();
+ mod z {
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_glob() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::*$0;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn remove_unused_braced_glob() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::{*}$0;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn dont_remove_used_glob() {
+ check_assist_not_applicable(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::*$0;
+
+ fn f() {
+ let x = X();
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn only_remove_from_selection() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+struct Y();
+mod z {
+ $0use super::X;$0
+ use super::Y;
+}
+mod w {
+ use super::Y;
+}
+"#,
+ r#"
+struct X();
+struct Y();
+mod z {
+ use super::Y;
+}
+mod w {
+ use super::Y;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn test_several_files() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+//- /foo.rs
+pub struct X();
+pub struct Y();
+
+//- /main.rs
+$0use foo::X;
+use foo::Y;
+$0
+mod foo;
+mod z {
+ use crate::foo::X;
+}
+"#,
+ r#"
+
+mod foo;
+mod z {
+ use crate::foo::X;
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn use_in_submodule_doesnt_count() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+struct X();
+mod z {
+ use super::X$0;
+
+ mod w {
+ use crate::X;
+
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ r#"
+struct X();
+mod z {
+
+ mod w {
+ use crate::X;
+
+ fn f() {
+ let x = X();
+ }
+ }
+}
+"#,
+ );
+ }
+
+ #[test]
+ fn use_in_submodule_file_doesnt_count() {
+ check_assist(
+ remove_unused_imports,
+ r#"
+//- /z/foo.rs
+use crate::X;
+fn f() {
+ let x = X();
+}
+
+//- /main.rs
+pub struct X();
+
+mod z {
+ use crate::X$0;
+ mod foo;
+}
+"#,
+ r#"
+pub struct X();
+
+mod z {
+ mod foo;
+}
+"#,
+ );
+ }
+}
diff --git a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
index e7b62d49bb..c7c0be4c7d 100644
--- a/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
+++ b/crates/ide-assists/src/handlers/replace_named_generic_with_impl.rs
@@ -157,7 +157,7 @@ fn find_usages(
file_id: FileId,
) -> UsageSearchResult {
let file_range = FileRange { file_id, range: fn_.syntax().text_range() };
- type_param_def.usages(sema).in_scope(SearchScope::file_range(file_range)).all()
+ type_param_def.usages(sema).in_scope(&SearchScope::file_range(file_range)).all()
}
fn check_valid_usages(usages: &UsageSearchResult, param_list_range: TextRange) -> bool {
diff --git a/crates/ide-assists/src/lib.rs b/crates/ide-assists/src/lib.rs
index a82f1f9dd8..2ebb5ef9b1 100644
--- a/crates/ide-assists/src/lib.rs
+++ b/crates/ide-assists/src/lib.rs
@@ -184,6 +184,7 @@ mod handlers {
mod raw_string;
mod remove_dbg;
mod remove_mut;
+ mod remove_unused_imports;
mod remove_unused_param;
mod remove_parentheses;
mod reorder_fields;
@@ -294,6 +295,7 @@ mod handlers {
raw_string::make_usual_string,
raw_string::remove_hash,
remove_mut::remove_mut,
+ remove_unused_imports::remove_unused_imports,
remove_unused_param::remove_unused_param,
remove_parentheses::remove_parentheses,
reorder_fields::reorder_fields,
diff --git a/crates/ide-assists/src/tests/generated.rs b/crates/ide-assists/src/tests/generated.rs
index 4d47a199b7..ec3822c3d1 100644
--- a/crates/ide-assists/src/tests/generated.rs
+++ b/crates/ide-assists/src/tests/generated.rs
@@ -2234,6 +2234,24 @@ fn main() {
}
#[test]
+fn doctest_remove_unused_imports() {
+ check_doc_test(
+ "remove_unused_imports",
+ r#####"
+struct X();
+mod foo {
+ use super::X$0;
+}
+"#####,
+ r#####"
+struct X();
+mod foo {
+}
+"#####,
+ )
+}
+
+#[test]
fn doctest_remove_unused_param() {
check_doc_test(
"remove_unused_param",
diff --git a/crates/ide-db/src/search.rs b/crates/ide-db/src/search.rs
index f3c0f79c58..d5abd09912 100644
--- a/crates/ide-db/src/search.rs
+++ b/crates/ide-db/src/search.rs
@@ -127,7 +127,7 @@ impl SearchScope {
}
/// Build a search scope spanning the given module and all its submodules.
- fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
+ pub fn module_and_children(db: &RootDatabase, module: hir::Module) -> SearchScope {
let mut entries = IntMap::default();
let (file_id, range) = {
@@ -329,7 +329,7 @@ impl Definition {
pub struct FindUsages<'a> {
def: Definition,
sema: &'a Semantics<'a, RootDatabase>,
- scope: Option<SearchScope>,
+ scope: Option<&'a SearchScope>,
/// The container of our definition should it be an assoc item
assoc_item_container: Option<hir::AssocItemContainer>,
/// whether to search for the `Self` type of the definition
@@ -338,7 +338,7 @@ pub struct FindUsages<'a> {
search_self_mod: bool,
}
-impl FindUsages<'_> {
+impl<'a> FindUsages<'a> {
/// Enable searching for `Self` when the definition is a type or `self` for modules.
pub fn include_self_refs(mut self) -> Self {
self.include_self_kw_refs = def_to_ty(self.sema, &self.def);
@@ -347,12 +347,12 @@ impl FindUsages<'_> {
}
/// Limit the search to a given [`SearchScope`].
- pub fn in_scope(self, scope: SearchScope) -> Self {
+ pub fn in_scope(self, scope: &'a SearchScope) -> Self {
self.set_scope(Some(scope))
}
/// Limit the search to a given [`SearchScope`].
- pub fn set_scope(mut self, scope: Option<SearchScope>) -> Self {
+ pub fn set_scope(mut self, scope: Option<&'a SearchScope>) -> Self {
assert!(self.scope.is_none());
self.scope = scope;
self
@@ -376,7 +376,7 @@ impl FindUsages<'_> {
res
}
- fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
+ pub fn search(&self, sink: &mut dyn FnMut(FileId, FileReference) -> bool) {
let _p = profile::span("FindUsages:search");
let sema = self.sema;
diff --git a/crates/ide-ssr/src/search.rs b/crates/ide-ssr/src/search.rs
index 96c193bd53..ca76d0a87b 100644
--- a/crates/ide-ssr/src/search.rs
+++ b/crates/ide-ssr/src/search.rs
@@ -121,7 +121,7 @@ impl MatchFinder<'_> {
// cache miss. This is a limitation of NLL and is fixed with Polonius. For now we do two
// lookups in the case of a cache hit.
if usage_cache.find(&definition).is_none() {
- let usages = definition.usages(&self.sema).in_scope(self.search_scope()).all();
+ let usages = definition.usages(&self.sema).in_scope(&self.search_scope()).all();
usage_cache.usages.push((definition, usages));
return &usage_cache.usages.last().unwrap().1;
}
diff --git a/crates/ide/src/highlight_related.rs b/crates/ide/src/highlight_related.rs
index 7e545491f8..43e89a334b 100644
--- a/crates/ide/src/highlight_related.rs
+++ b/crates/ide/src/highlight_related.rs
@@ -100,10 +100,7 @@ fn highlight_closure_captures(
.flat_map(|local| {
let usages = Definition::Local(local)
.usages(sema)
- .set_scope(Some(SearchScope::file_range(FileRange {
- file_id,
- range: search_range,
- })))
+ .in_scope(&SearchScope::file_range(FileRange { file_id, range: search_range }))
.include_self_refs()
.all()
.references
@@ -139,7 +136,7 @@ fn highlight_references(
.iter()
.filter_map(|&d| {
d.usages(sema)
- .set_scope(Some(SearchScope::single_file(file_id)))
+ .in_scope(&SearchScope::single_file(file_id))
.include_self_refs()
.all()
.references
@@ -183,7 +180,7 @@ fn highlight_references(
.filter_map(|item| {
Definition::from(item)
.usages(sema)
- .set_scope(Some(SearchScope::file_range(FileRange {
+ .set_scope(Some(&SearchScope::file_range(FileRange {
file_id,
range: trait_item_use_scope.text_range(),
})))
diff --git a/crates/ide/src/references.rs b/crates/ide/src/references.rs
index fdc5261ac3..44073fa757 100644
--- a/crates/ide/src/references.rs
+++ b/crates/ide/src/references.rs
@@ -74,7 +74,7 @@ pub(crate) fn find_all_refs(
}
});
let mut usages =
- def.usages(sema).set_scope(search_scope.clone()).include_self_refs().all();
+ def.usages(sema).set_scope(search_scope.as_ref()).include_self_refs().all();
if literal_search {
retain_adt_literal_usages(&mut usages, def, sema);
diff --git a/crates/ide/src/runnables.rs b/crates/ide/src/runnables.rs
index 9fa0e6449b..8c445ddcb2 100644
--- a/crates/ide/src/runnables.rs
+++ b/crates/ide/src/runnables.rs
@@ -232,7 +232,7 @@ fn find_related_tests(
for def in defs {
let defs = def
.usages(sema)
- .set_scope(search_scope.clone())
+ .set_scope(search_scope.as_ref())
.all()
.references
.into_values()
diff --git a/crates/syntax/src/ast/edit_in_place.rs b/crates/syntax/src/ast/edit_in_place.rs
index 606804aea2..a150d9e6c0 100644
--- a/crates/syntax/src/ast/edit_in_place.rs
+++ b/crates/syntax/src/ast/edit_in_place.rs
@@ -380,6 +380,26 @@ impl Removable for ast::UseTree {
}
impl ast::UseTree {
+ /// Deletes the usetree node represented by the input. Recursively removes parents, including use nodes that become empty.
+ pub fn remove_recursive(self) {
+ let parent = self.syntax().parent();
+
+ self.remove();
+
+ if let Some(u) = parent.clone().and_then(ast::Use::cast) {
+ if u.use_tree().is_none() {
+ u.remove();
+ }
+ } else if let Some(u) = parent.and_then(ast::UseTreeList::cast) {
+ if u.use_trees().next().is_none() {
+ let parent = u.syntax().parent().and_then(ast::UseTree::cast);
+ if let Some(u) = parent {
+ u.remove_recursive();
+ }
+ }
+ }
+ }
+
pub fn get_or_create_use_tree_list(&self) -> ast::UseTreeList {
match self.use_tree_list() {
Some(it) => it,
@@ -487,6 +507,22 @@ impl Removable for ast::Use {
}
}
}
+ let prev_ws = self
+ .syntax()
+ .prev_sibling_or_token()
+ .and_then(|it| it.into_token())
+ .and_then(ast::Whitespace::cast);
+ if let Some(prev_ws) = prev_ws {
+ let ws_text = prev_ws.syntax().text();
+ let prev_newline = ws_text.rfind('\n').map(|x| x + 1).unwrap_or(0);
+ let rest = &ws_text[0..prev_newline];
+ if rest.is_empty() {
+ ted::remove(prev_ws.syntax());
+ } else {
+ ted::replace(prev_ws.syntax(), make::tokens::whitespace(rest));
+ }
+ }
+
ted::remove(self.syntax());
}
}