Skip to content

Commit bb72150

Browse files
committed
Handle more cases in AST replacing in expand glob import
1 parent 128eef7 commit bb72150

File tree

2 files changed

+220
-35
lines changed

2 files changed

+220
-35
lines changed

crates/assists/src/handlers/expand_glob_import.rs

Lines changed: 219 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
use either::Either;
2+
use std::iter::successors;
3+
24
use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
35
use ide_db::{
46
defs::{classify_name_ref, Definition, NameRefClass},
57
RootDatabase,
68
};
7-
use syntax::{ast, AstNode, SyntaxToken, T};
9+
use syntax::{algo, ast, AstNode, SourceFile, SyntaxNode, SyntaxToken, T};
810

911
use crate::{
1012
assist_context::{AssistBuilder, AssistContext, Assists},
@@ -48,27 +50,39 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti
4850
let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
4951

5052
let defs_in_mod = find_defs_in_mod(ctx, scope, module)?;
51-
let name_refs_in_source_file =
52-
source_file.syntax().descendants().filter_map(ast::NameRef::cast).collect();
53-
let used_names = find_used_names(ctx, defs_in_mod, name_refs_in_source_file);
53+
let names_to_import = find_names_to_import(ctx, source_file, defs_in_mod);
5454

55-
let target = parent.syntax();
55+
let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
5656
acc.add(
5757
AssistId("expand_glob_import", AssistKind::RefactorRewrite),
5858
"Expand glob import",
5959
target.text_range(),
6060
|builder| {
61-
replace_ast(builder, parent, mod_path, used_names);
61+
replace_ast(builder, parent, mod_path, names_to_import);
6262
},
6363
)
6464
}
6565

66-
fn find_parent_and_path(star: &SyntaxToken) -> Option<(ast::UseTree, ast::Path)> {
67-
star.ancestors().find_map(|n| {
66+
fn find_parent_and_path(
67+
star: &SyntaxToken,
68+
) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
69+
return star.ancestors().find_map(|n| {
70+
find_use_tree_list(n.clone())
71+
.and_then(|(u, p)| Some((Either::Right(u), p)))
72+
.or_else(|| find_use_tree(n).and_then(|(u, p)| Some((Either::Left(u), p))))
73+
});
74+
75+
fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> {
76+
let use_tree_list = ast::UseTreeList::cast(n)?;
77+
let path = use_tree_list.parent_use_tree().path()?;
78+
Some((use_tree_list, path))
79+
}
80+
81+
fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> {
6882
let use_tree = ast::UseTree::cast(n)?;
6983
let path = use_tree.path()?;
7084
Some((use_tree, path))
71-
})
85+
}
7286
}
7387

7488
#[derive(PartialEq)]
@@ -105,14 +119,36 @@ fn find_defs_in_mod(
105119
Some(defs)
106120
}
107121

108-
fn find_used_names(
122+
fn find_names_to_import(
109123
ctx: &AssistContext,
124+
source_file: &SourceFile,
110125
defs_in_mod: Vec<Def>,
111-
name_refs_in_source_file: Vec<ast::NameRef>,
112126
) -> Vec<Name> {
113-
let defs_in_source_file = name_refs_in_source_file
114-
.iter()
115-
.filter_map(|r| classify_name_ref(&ctx.sema, r))
127+
let (name_refs_in_use_item, name_refs_in_source) = source_file
128+
.syntax()
129+
.descendants()
130+
.filter_map(|n| {
131+
let name_ref = ast::NameRef::cast(n.clone())?;
132+
let name_ref_class = classify_name_ref(&ctx.sema, &name_ref)?;
133+
let is_in_use_item =
134+
successors(n.parent(), |n| n.parent()).find_map(ast::Use::cast).is_some();
135+
Some((name_ref_class, is_in_use_item))
136+
})
137+
.partition::<Vec<_>, _>(|&(_, is_in_use_item)| is_in_use_item);
138+
139+
let name_refs_to_import: Vec<NameRefClass> = name_refs_in_source
140+
.into_iter()
141+
.filter_map(|(r, _)| {
142+
if name_refs_in_use_item.contains(&(r.clone(), true)) {
143+
// already imported
144+
return None;
145+
}
146+
Some(r)
147+
})
148+
.collect();
149+
150+
let defs_in_source_file = name_refs_to_import
151+
.into_iter()
116152
.filter_map(|rc| match rc {
117153
NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
118154
NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
@@ -141,28 +177,62 @@ fn find_used_names(
141177

142178
fn replace_ast(
143179
builder: &mut AssistBuilder,
144-
parent: ast::UseTree,
180+
parent: Either<ast::UseTree, ast::UseTreeList>,
145181
path: ast::Path,
146-
used_names: Vec<Name>,
182+
names_to_import: Vec<Name>,
147183
) {
148-
let replacement = match used_names.as_slice() {
149-
[name] => ast::make::use_tree(
150-
ast::make::path_from_text(&format!("{}::{}", path, name)),
151-
None,
152-
None,
153-
false,
154-
),
155-
names => ast::make::use_tree(
156-
path,
157-
Some(ast::make::use_tree_list(names.iter().map(|n| {
158-
ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
159-
}))),
160-
None,
161-
false,
162-
),
184+
let existing_use_trees = match parent.clone() {
185+
Either::Left(_) => vec![],
186+
Either::Right(u) => u.use_trees().filter(|n|
187+
// filter out star
188+
n.star_token().is_none()
189+
).collect(),
163190
};
164191

165-
builder.replace_ast(parent, replacement);
192+
let new_use_trees: Vec<ast::UseTree> = names_to_import
193+
.iter()
194+
.map(|n| ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false))
195+
.collect();
196+
197+
let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
198+
199+
match use_trees.as_slice() {
200+
[name] => {
201+
if let Some(end_path) = name.path() {
202+
let replacement = ast::make::use_tree(
203+
ast::make::path_from_text(&format!("{}::{}", path, end_path)),
204+
None,
205+
None,
206+
false,
207+
);
208+
209+
algo::diff(
210+
&parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
211+
replacement.syntax(),
212+
)
213+
.into_text_edit(builder.text_edit_builder());
214+
}
215+
}
216+
names => {
217+
let replacement = match parent {
218+
Either::Left(_) => ast::make::use_tree(
219+
path,
220+
Some(ast::make::use_tree_list(names.to_owned())),
221+
None,
222+
false,
223+
)
224+
.syntax()
225+
.clone(),
226+
Either::Right(_) => ast::make::use_tree_list(names.to_owned()).syntax().clone(),
227+
};
228+
229+
algo::diff(
230+
&parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
231+
&replacement,
232+
)
233+
.into_text_edit(builder.text_edit_builder());
234+
}
235+
};
166236
}
167237

168238
#[cfg(test)]
@@ -236,7 +306,46 @@ mod foo {
236306
pub fn f() {}
237307
}
238308
239-
use foo::{Baz, Bar, f};
309+
use foo::{f, Baz, Bar};
310+
311+
fn qux(bar: Bar, baz: Baz) {
312+
f();
313+
}
314+
",
315+
)
316+
}
317+
318+
#[test]
319+
fn expanding_glob_import_with_existing_uses_in_same_module() {
320+
check_assist(
321+
expand_glob_import,
322+
r"
323+
mod foo {
324+
pub struct Bar;
325+
pub struct Baz;
326+
pub struct Qux;
327+
328+
pub fn f() {}
329+
}
330+
331+
use foo::Bar;
332+
use foo::{*<|>, f};
333+
334+
fn qux(bar: Bar, baz: Baz) {
335+
f();
336+
}
337+
",
338+
r"
339+
mod foo {
340+
pub struct Bar;
341+
pub struct Baz;
342+
pub struct Qux;
343+
344+
pub fn f() {}
345+
}
346+
347+
use foo::Bar;
348+
use foo::{f, Baz};
240349
241350
fn qux(bar: Bar, baz: Baz) {
242351
f();
@@ -286,7 +395,7 @@ mod foo {
286395
}
287396
}
288397
289-
use foo::{bar::{Baz, Bar, f}, baz::*};
398+
use foo::{bar::{f, Baz, Bar}, baz::*};
290399
291400
fn qux(bar: Bar, baz: Baz) {
292401
f();
@@ -486,6 +595,82 @@ use foo::{
486595
baz::{g, qux::{h, q::j}}
487596
};
488597
598+
fn qux(bar: Bar, baz: Baz) {
599+
f();
600+
g();
601+
h();
602+
j();
603+
}
604+
",
605+
);
606+
607+
check_assist(
608+
expand_glob_import,
609+
r"
610+
mod foo {
611+
pub mod bar {
612+
pub struct Bar;
613+
pub struct Baz;
614+
pub struct Qux;
615+
616+
pub fn f() {}
617+
}
618+
619+
pub mod baz {
620+
pub fn g() {}
621+
622+
pub mod qux {
623+
pub fn h() {}
624+
pub fn m() {}
625+
626+
pub mod q {
627+
pub fn j() {}
628+
}
629+
}
630+
}
631+
}
632+
633+
use foo::{
634+
bar::{*, f},
635+
baz::{g, qux::{q::j, *<|>}}
636+
};
637+
638+
fn qux(bar: Bar, baz: Baz) {
639+
f();
640+
g();
641+
h();
642+
j();
643+
}
644+
",
645+
r"
646+
mod foo {
647+
pub mod bar {
648+
pub struct Bar;
649+
pub struct Baz;
650+
pub struct Qux;
651+
652+
pub fn f() {}
653+
}
654+
655+
pub mod baz {
656+
pub fn g() {}
657+
658+
pub mod qux {
659+
pub fn h() {}
660+
pub fn m() {}
661+
662+
pub mod q {
663+
pub fn j() {}
664+
}
665+
}
666+
}
667+
}
668+
669+
use foo::{
670+
bar::{*, f},
671+
baz::{g, qux::{q::j, h}}
672+
};
673+
489674
fn qux(bar: Bar, baz: Baz) {
490675
f();
491676
g();

crates/ide_db/src/defs.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -225,7 +225,7 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option
225225
}
226226
}
227227

228-
#[derive(Debug)]
228+
#[derive(Debug, Clone, PartialEq, Eq)]
229229
pub enum NameRefClass {
230230
ExternCrate(Crate),
231231
Definition(Definition),

0 commit comments

Comments
 (0)