From edd79a6b1c6e019d69d8d1304686391ea9cb4209 Mon Sep 17 00:00:00 2001 From: unexge Date: Sun, 2 Aug 2020 22:56:54 +0300 Subject: Add expand glob import assist --- crates/ra_assists/src/assist_context.rs | 4 + .../ra_assists/src/handlers/expand_glob_import.rs | 359 +++++++++++++++++++++ crates/ra_assists/src/lib.rs | 2 + crates/ra_syntax/src/ast/make.rs | 2 +- 4 files changed, 366 insertions(+), 1 deletion(-) create mode 100644 crates/ra_assists/src/handlers/expand_glob_import.rs diff --git a/crates/ra_assists/src/assist_context.rs b/crates/ra_assists/src/assist_context.rs index 3407df856..afd3fd4b9 100644 --- a/crates/ra_assists/src/assist_context.rs +++ b/crates/ra_assists/src/assist_context.rs @@ -73,6 +73,10 @@ impl<'a> AssistContext<'a> { self.sema.db } + pub(crate) fn source_file(&self) -> &SourceFile { + &self.source_file + } + // NB, this ignores active selection. pub(crate) fn offset(&self) -> TextSize { self.frange.range.start() diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs new file mode 100644 index 000000000..590239304 --- /dev/null +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -0,0 +1,359 @@ +use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; +use ra_ide_db::{ + defs::{classify_name_ref, Definition, NameRefClass}, + RootDatabase, +}; +use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; + +use crate::{ + assist_context::{AssistBuilder, AssistContext, Assists}, + AssistId, AssistKind, +}; + +// Assist: expand_glob_import +// +// Expands glob imports. +// +// ``` +// mod foo { +// pub struct Bar; +// pub struct Baz; +// } +// +// use foo::*<|>; +// +// fn qux(bar: Bar, baz: Baz) {} +// ``` +// -> +// ``` +// mod foo { +// pub struct Bar; +// pub struct Baz; +// } +// +// use foo::{Bar, Baz}; +// +// fn qux(bar: Bar, baz: Baz) {} +// ``` +pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> { + let star = ctx.find_token_at_offset(T![*])?; + let mod_path = find_mod_path(&star)?; + + let source_file = ctx.source_file(); + let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); + + let defs_in_mod = find_defs_in_mod(ctx, scope, &mod_path)?; + let name_refs_in_source_file = + source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect(); + let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file); + + let parent = star.parent().parent()?; + acc.add( + AssistId("expand_glob_import", AssistKind::RefactorRewrite), + "Expand glob import", + parent.text_range(), + |builder| { + replace_ast(builder, &parent, mod_path, used_names); + }, + ) +} + +fn find_mod_path(star: &SyntaxToken) -> Option { + let mut node = star.parent(); + + loop { + match_ast! { + match node { + ast::UseTree(use_tree) => { + if let Some(path) = use_tree.path() { + return Some(path); + } + }, + ast::UseTreeList(_use_tree_list) => {}, + _ => return None, + } + } + + node = match node.parent() { + Some(node) => node, + None => return None, + } + } +} + +#[derive(PartialEq)] +enum Def { + ModuleDef(ModuleDef), + MacroDef(MacroDef), +} + +impl Def { + fn name(&self, db: &RootDatabase) -> Option { + match self { + Def::ModuleDef(def) => def.name(db), + Def::MacroDef(def) => def.name(db), + } + } +} + +fn find_defs_in_mod( + ctx: &AssistContext, + from: SemanticsScope<'_>, + path: &ast::Path, +) -> Option> { + let hir_path = ctx.sema.lower_path(&path)?; + let module = if let Some(PathResolution::Def(ModuleDef::Module(module))) = + from.resolve_hir_path_qualifier(&hir_path) + { + module + } else { + return None; + }; + + let module_scope = module.scope(ctx.db(), from.module()); + + let mut defs = vec![]; + for (_, def) in module_scope { + match def { + ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)), + ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)), + _ => continue, + } + } + + Some(defs) +} + +fn find_used_names( + ctx: &AssistContext, + defs_in_mod: Vec, + name_refs_in_source_file: Vec, +) -> Vec { + let defs_in_source_file = name_refs_in_source_file + .iter() + .filter_map(|r| classify_name_ref(&ctx.sema, r)) + .filter_map(|rc| match rc { + NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), + NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), + _ => None, + }) + .collect::>(); + + defs_in_mod + .iter() + .filter(|d| defs_in_source_file.contains(d)) + .filter_map(|d| d.name(ctx.db())) + .collect() +} + +fn replace_ast( + builder: &mut AssistBuilder, + node: &SyntaxNode, + path: ast::Path, + used_names: Vec, +) { + let new_use_tree_list = ast::make::use_tree_list(used_names.iter().map(|n| { + ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) + })); + + match_ast! { + match node { + ast::UseTree(use_tree) => { + builder.replace_ast(use_tree, make_use_tree(path, new_use_tree_list)); + }, + ast::UseTreeList(use_tree_list) => { + builder.replace_ast(use_tree_list, new_use_tree_list); + }, + ast::UseItem(use_item) => { + builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list))); + }, + _ => {}, + } + } + + fn make_use_tree(path: ast::Path, use_tree_list: ast::UseTreeList) -> ast::UseTree { + ast::make::use_tree(path, Some(use_tree_list), None, false) + } +} + +#[cfg(test)] +mod tests { + use crate::tests::{check_assist, check_assist_not_applicable}; + + use super::*; + + #[test] + fn expanding_glob_import() { + check_assist( + expand_glob_import, + r" +mod foo { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} +} + +use foo::*<|>; + +fn qux(bar: Bar, baz: Baz) { + f(); +} +", + r" +mod foo { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} +} + +use foo::{Baz, Bar, f}; + +fn qux(bar: Bar, baz: Baz) { + f(); +} +", + ) + } + + #[test] + fn expanding_glob_import_with_existing_explicit_names() { + check_assist( + expand_glob_import, + r" +mod foo { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} +} + +use foo::{*<|>, f}; + +fn qux(bar: Bar, baz: Baz) { + f(); +} +", + r" +mod foo { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} +} + +use foo::{Baz, Bar, f}; + +fn qux(bar: Bar, baz: Baz) { + f(); +} +", + ) + } + + #[test] + fn expanding_nested_glob_import() { + check_assist( + expand_glob_import, + r" +mod foo { + mod bar { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} + } + + mod baz { + pub fn g() {} + } +} + +use foo::{bar::{*<|>, f}, baz::*}; + +fn qux(bar: Bar, baz: Baz) { + f(); + g(); +} +", + r" +mod foo { + mod bar { + pub struct Bar; + pub struct Baz; + pub struct Qux; + + pub fn f() {} + } + + mod baz { + pub fn g() {} + } +} + +use foo::{bar::{Baz, Bar, f}, baz::*}; + +fn qux(bar: Bar, baz: Baz) { + f(); + g(); +} +", + ) + } + + #[test] + fn expanding_glob_import_with_macro_defs() { + check_assist( + expand_glob_import, + r" +//- /lib.rs crate:foo +#[macro_export] +macro_rules! bar { + () => () +} + +pub fn baz() {} + +//- /main.rs crate:main deps:foo +use foo::*<|>; + +fn main() { + bar!(); + baz(); +} +", + r" +use foo::{bar, baz}; + +fn main() { + bar!(); + baz(); +} +", + ) + } + + #[test] + fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() { + check_assist_not_applicable( + expand_glob_import, + r" + mod foo { + pub struct Bar; + pub struct Baz; + pub struct Qux; + } + + use foo::Bar<|>; + + fn qux(bar: Bar, baz: Baz) {} + ", + ) + } +} diff --git a/crates/ra_assists/src/lib.rs b/crates/ra_assists/src/lib.rs index 465b90415..507646cc8 100644 --- a/crates/ra_assists/src/lib.rs +++ b/crates/ra_assists/src/lib.rs @@ -140,6 +140,7 @@ mod handlers { mod change_return_type_to_result; mod change_visibility; mod early_return; + mod expand_glob_import; mod extract_struct_from_enum_variant; mod extract_variable; mod fill_match_arms; @@ -181,6 +182,7 @@ mod handlers { change_return_type_to_result::change_return_type_to_result, change_visibility::change_visibility, early_return::convert_to_guarded_return, + expand_glob_import::expand_glob_import, extract_struct_from_enum_variant::extract_struct_from_enum_variant, extract_variable::extract_variable, fill_match_arms::fill_match_arms, diff --git a/crates/ra_syntax/src/ast/make.rs b/crates/ra_syntax/src/ast/make.rs index 673777015..3cb1d35ee 100644 --- a/crates/ra_syntax/src/ast/make.rs +++ b/crates/ra_syntax/src/ast/make.rs @@ -30,7 +30,7 @@ pub fn path_unqualified(segment: ast::PathSegment) -> ast::Path { pub fn path_qualified(qual: ast::Path, segment: ast::PathSegment) -> ast::Path { path_from_text(&format!("{}::{}", qual, segment)) } -fn path_from_text(text: &str) -> ast::Path { +pub fn path_from_text(text: &str) -> ast::Path { ast_from_text(text) } -- cgit v1.2.3 From a05a2ab1bb942c7b950f270ff1a31ba67518ae14 Mon Sep 17 00:00:00 2001 From: unexge Date: Sun, 2 Aug 2020 23:07:36 +0300 Subject: Rename ast::UseItem to ast::Use --- crates/ra_assists/src/handlers/expand_glob_import.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs index 590239304..ae9e86181 100644 --- a/crates/ra_assists/src/handlers/expand_glob_import.rs +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -164,7 +164,7 @@ fn replace_ast( ast::UseTreeList(use_tree_list) => { builder.replace_ast(use_tree_list, new_use_tree_list); }, - ast::UseItem(use_item) => { + ast::Use(use_item) => { builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list))); }, _ => {}, -- cgit v1.2.3 From 544322e66aacb763f8c824b5a572f8d7b93c9a6f Mon Sep 17 00:00:00 2001 From: unexge Date: Sun, 2 Aug 2020 23:07:56 +0300 Subject: Generate doctest --- .../ra_assists/src/handlers/expand_glob_import.rs | 2 +- crates/ra_assists/src/tests/generated.rs | 27 ++++++++++++++++++++++ 2 files changed, 28 insertions(+), 1 deletion(-) diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs index ae9e86181..963c1353f 100644 --- a/crates/ra_assists/src/handlers/expand_glob_import.rs +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -31,7 +31,7 @@ use crate::{ // pub struct Baz; // } // -// use foo::{Bar, Baz}; +// use foo::{Baz, Bar}; // // fn qux(bar: Bar, baz: Baz) {} // ``` diff --git a/crates/ra_assists/src/tests/generated.rs b/crates/ra_assists/src/tests/generated.rs index eff7feded..97978e7a2 100644 --- a/crates/ra_assists/src/tests/generated.rs +++ b/crates/ra_assists/src/tests/generated.rs @@ -228,6 +228,33 @@ fn main() { ) } +#[test] +fn doctest_expand_glob_import() { + check_doc_test( + "expand_glob_import", + r#####" +mod foo { + pub struct Bar; + pub struct Baz; +} + +use foo::*<|>; + +fn qux(bar: Bar, baz: Baz) {} +"#####, + r#####" +mod foo { + pub struct Bar; + pub struct Baz; +} + +use foo::{Baz, Bar}; + +fn qux(bar: Bar, baz: Baz) {} +"#####, + ) +} + #[test] fn doctest_extract_struct_from_enum_variant() { check_doc_test( -- cgit v1.2.3 From bdb97756ca4c2710061fc49a5e2c2c10f5cc1eb6 Mon Sep 17 00:00:00 2001 From: unexge Date: Mon, 3 Aug 2020 13:04:20 +0300 Subject: Simplify `find_mod_path` with use of `node.ancestors` --- .../ra_assists/src/handlers/expand_glob_import.rs | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs index 963c1353f..1eb8070ad 100644 --- a/crates/ra_assists/src/handlers/expand_glob_import.rs +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -59,26 +59,7 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti } fn find_mod_path(star: &SyntaxToken) -> Option { - let mut node = star.parent(); - - loop { - match_ast! { - match node { - ast::UseTree(use_tree) => { - if let Some(path) = use_tree.path() { - return Some(path); - } - }, - ast::UseTreeList(_use_tree_list) => {}, - _ => return None, - } - } - - node = match node.parent() { - Some(node) => node, - None => return None, - } - } + star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path())) } #[derive(PartialEq)] -- cgit v1.2.3 From 5214b4cdba2c4976cb1ca4724a008268cdaf3e61 Mon Sep 17 00:00:00 2001 From: unexge Date: Wed, 5 Aug 2020 11:29:00 +0300 Subject: Look for trait methods in expand glob import assist --- .../ra_assists/src/handlers/expand_glob_import.rs | 78 ++++++++++++++++++---- 1 file changed, 65 insertions(+), 13 deletions(-) diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs index 1eb8070ad..978c6772e 100644 --- a/crates/ra_assists/src/handlers/expand_glob_import.rs +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -1,15 +1,17 @@ -use hir::{MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; +use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; use ra_ide_db::{ defs::{classify_name_ref, Definition, NameRefClass}, RootDatabase, }; -use ra_syntax::{ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; +use ra_syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T}; use crate::{ assist_context::{AssistBuilder, AssistContext, Assists}, AssistId, AssistKind, }; +use either::Either; + // Assist: expand_glob_import // // Expands glob imports. @@ -122,7 +124,19 @@ fn find_used_names( defs_in_mod .iter() - .filter(|d| defs_in_source_file.contains(d)) + .filter(|def| { + if let Def::ModuleDef(ModuleDef::Trait(tr)) = def { + for item in tr.items(ctx.db()) { + if let AssocItem::Function(f) = item { + if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) { + return true; + } + } + } + } + + defs_in_source_file.contains(def) + }) .filter_map(|d| d.name(ctx.db())) .collect() } @@ -133,28 +147,38 @@ fn replace_ast( path: ast::Path, used_names: Vec, ) { - let new_use_tree_list = ast::make::use_tree_list(used_names.iter().map(|n| { - ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) - })); + let replacement: Either = if used_names.len() == 1 { + Either::Left(ast::make::use_tree( + ast::make::path_from_text(&format!("{}::{}", path, used_names.first().unwrap())), + None, + None, + false, + )) + } else { + Either::Right(ast::make::use_tree_list(used_names.iter().map(|n| { + ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) + }))) + }; + + let mut replace_node = |replacement: Either| { + algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone())) + .into_text_edit(builder.text_edit_builder()); + }; match_ast! { match node { ast::UseTree(use_tree) => { - builder.replace_ast(use_tree, make_use_tree(path, new_use_tree_list)); + replace_node(replacement); }, ast::UseTreeList(use_tree_list) => { - builder.replace_ast(use_tree_list, new_use_tree_list); + replace_node(replacement); }, ast::Use(use_item) => { - builder.replace_ast(use_item, ast::make::use_item(make_use_tree(path, new_use_tree_list))); + builder.replace_ast(use_item, ast::make::use_item(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false)))); }, _ => {}, } } - - fn make_use_tree(path: ast::Path, use_tree_list: ast::UseTreeList) -> ast::UseTree { - ast::make::use_tree(path, Some(use_tree_list), None, false) - } } #[cfg(test)] @@ -320,6 +344,34 @@ fn main() { ) } + #[test] + fn expanding_glob_import_with_trait_method_uses() { + check_assist( + expand_glob_import, + r" +//- /lib.rs crate:foo +pub trait Tr { + fn method(&self) {} +} +impl Tr for () {} + +//- /main.rs crate:main deps:foo +use foo::*<|>; + +fn main() { + ().method(); +} +", + r" +use foo::Tr; + +fn main() { + ().method(); +} +", + ) + } + #[test] fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() { check_assist_not_applicable( -- cgit v1.2.3 From 6cb090345ece4a97c640159240594f3902e37032 Mon Sep 17 00:00:00 2001 From: unexge Date: Wed, 5 Aug 2020 13:25:26 +0300 Subject: Pattern match on slice elements instead of using `.first().unwrap()` --- crates/ra_assists/src/handlers/expand_glob_import.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/crates/ra_assists/src/handlers/expand_glob_import.rs b/crates/ra_assists/src/handlers/expand_glob_import.rs index 978c6772e..69f6b3674 100644 --- a/crates/ra_assists/src/handlers/expand_glob_import.rs +++ b/crates/ra_assists/src/handlers/expand_glob_import.rs @@ -147,17 +147,16 @@ fn replace_ast( path: ast::Path, used_names: Vec, ) { - let replacement: Either = if used_names.len() == 1 { - Either::Left(ast::make::use_tree( - ast::make::path_from_text(&format!("{}::{}", path, used_names.first().unwrap())), + let replacement: Either = match used_names.as_slice() { + [name] => Either::Left(ast::make::use_tree( + ast::make::path_from_text(&format!("{}::{}", path, name)), None, None, false, - )) - } else { - Either::Right(ast::make::use_tree_list(used_names.iter().map(|n| { + )), + names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| { ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false) - }))) + }))), }; let mut replace_node = |replacement: Either| { -- cgit v1.2.3