Add expand glob import assist

This commit is contained in:
unexge 2020-08-02 22:56:54 +03:00
parent e96bfd812a
commit edd79a6b1c
4 changed files with 366 additions and 1 deletions

View File

@ -73,6 +73,10 @@ pub(crate) fn db(&self) -> &RootDatabase {
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()

View File

@ -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<ast::Path> {
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<Name> {
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<Vec<Def>> {
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<Def>,
name_refs_in_source_file: Vec<ast::NameRef>,
) -> Vec<Name> {
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::<Vec<Def>>();
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<Name>,
) {
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) {}
",
)
}
}

View File

@ -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 @@ pub(crate) fn all() -> &'static [Handler] {
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,

View File

@ -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)
}