diff options
author | unexge <[email protected]> | 2020-08-14 20:10:49 +0100 |
---|---|---|
committer | unexge <[email protected]> | 2020-08-20 19:34:53 +0100 |
commit | bb72150f0214c9395c48fdd3ee93650f0a507112 (patch) | |
tree | e908449d3b1faf038e963611877e09e480ffb538 | |
parent | 128eef779f572a8120cb475d86a373ac4c9e5559 (diff) |
Handle more cases in AST replacing in expand glob import
-rw-r--r-- | crates/assists/src/handlers/expand_glob_import.rs | 253 | ||||
-rw-r--r-- | crates/ide_db/src/defs.rs | 2 |
2 files changed, 220 insertions, 35 deletions
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs index 0288d1c77..ff9c80d49 100644 --- a/crates/assists/src/handlers/expand_glob_import.rs +++ b/crates/assists/src/handlers/expand_glob_import.rs | |||
@@ -1,10 +1,12 @@ | |||
1 | use either::Either; | 1 | use either::Either; |
2 | use std::iter::successors; | ||
3 | |||
2 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; | 4 | use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope}; |
3 | use ide_db::{ | 5 | use ide_db::{ |
4 | defs::{classify_name_ref, Definition, NameRefClass}, | 6 | defs::{classify_name_ref, Definition, NameRefClass}, |
5 | RootDatabase, | 7 | RootDatabase, |
6 | }; | 8 | }; |
7 | use syntax::{ast, AstNode, SyntaxToken, T}; | 9 | use syntax::{algo, ast, AstNode, SourceFile, SyntaxNode, SyntaxToken, T}; |
8 | 10 | ||
9 | use crate::{ | 11 | use crate::{ |
10 | assist_context::{AssistBuilder, AssistContext, Assists}, | 12 | assist_context::{AssistBuilder, AssistContext, Assists}, |
@@ -48,27 +50,39 @@ pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Opti | |||
48 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); | 50 | let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset()); |
49 | 51 | ||
50 | let defs_in_mod = find_defs_in_mod(ctx, scope, module)?; | 52 | let defs_in_mod = find_defs_in_mod(ctx, scope, module)?; |
51 | let name_refs_in_source_file = | 53 | let names_to_import = find_names_to_import(ctx, source_file, defs_in_mod); |
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); | ||
54 | 54 | ||
55 | let target = parent.syntax(); | 55 | let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone()); |
56 | acc.add( | 56 | acc.add( |
57 | AssistId("expand_glob_import", AssistKind::RefactorRewrite), | 57 | AssistId("expand_glob_import", AssistKind::RefactorRewrite), |
58 | "Expand glob import", | 58 | "Expand glob import", |
59 | target.text_range(), | 59 | target.text_range(), |
60 | |builder| { | 60 | |builder| { |
61 | replace_ast(builder, parent, mod_path, used_names); | 61 | replace_ast(builder, parent, mod_path, names_to_import); |
62 | }, | 62 | }, |
63 | ) | 63 | ) |
64 | } | 64 | } |
65 | 65 | ||
66 | fn find_parent_and_path(star: &SyntaxToken) -> Option<(ast::UseTree, ast::Path)> { | 66 | fn find_parent_and_path( |
67 | star.ancestors().find_map(|n| { | 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)> { | ||
68 | let use_tree = ast::UseTree::cast(n)?; | 82 | let use_tree = ast::UseTree::cast(n)?; |
69 | let path = use_tree.path()?; | 83 | let path = use_tree.path()?; |
70 | Some((use_tree, path)) | 84 | Some((use_tree, path)) |
71 | }) | 85 | } |
72 | } | 86 | } |
73 | 87 | ||
74 | #[derive(PartialEq)] | 88 | #[derive(PartialEq)] |
@@ -105,14 +119,36 @@ fn find_defs_in_mod( | |||
105 | Some(defs) | 119 | Some(defs) |
106 | } | 120 | } |
107 | 121 | ||
108 | fn find_used_names( | 122 | fn find_names_to_import( |
109 | ctx: &AssistContext, | 123 | ctx: &AssistContext, |
124 | source_file: &SourceFile, | ||
110 | defs_in_mod: Vec<Def>, | 125 | defs_in_mod: Vec<Def>, |
111 | name_refs_in_source_file: Vec<ast::NameRef>, | ||
112 | ) -> Vec<Name> { | 126 | ) -> Vec<Name> { |
113 | let defs_in_source_file = name_refs_in_source_file | 127 | let (name_refs_in_use_item, name_refs_in_source) = source_file |
114 | .iter() | 128 | .syntax() |
115 | .filter_map(|r| classify_name_ref(&ctx.sema, r)) | 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() | ||
116 | .filter_map(|rc| match rc { | 152 | .filter_map(|rc| match rc { |
117 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), | 153 | NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)), |
118 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), | 154 | NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)), |
@@ -141,28 +177,62 @@ fn find_used_names( | |||
141 | 177 | ||
142 | fn replace_ast( | 178 | fn replace_ast( |
143 | builder: &mut AssistBuilder, | 179 | builder: &mut AssistBuilder, |
144 | parent: ast::UseTree, | 180 | parent: Either<ast::UseTree, ast::UseTreeList>, |
145 | path: ast::Path, | 181 | path: ast::Path, |
146 | used_names: Vec<Name>, | 182 | names_to_import: Vec<Name>, |
147 | ) { | 183 | ) { |
148 | let replacement = match used_names.as_slice() { | 184 | let existing_use_trees = match parent.clone() { |
149 | [name] => ast::make::use_tree( | 185 | Either::Left(_) => vec![], |
150 | ast::make::path_from_text(&format!("{}::{}", path, name)), | 186 | Either::Right(u) => u.use_trees().filter(|n| |
151 | None, | 187 | // filter out star |
152 | None, | 188 | n.star_token().is_none() |
153 | false, | 189 | ).collect(), |
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 | ), | ||
163 | }; | 190 | }; |
164 | 191 | ||
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 | }; | ||
166 | } | 236 | } |
167 | 237 | ||
168 | #[cfg(test)] | 238 | #[cfg(test)] |
@@ -236,7 +306,46 @@ mod foo { | |||
236 | pub fn f() {} | 306 | pub fn f() {} |
237 | } | 307 | } |
238 | 308 | ||
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}; | ||
240 | 349 | ||
241 | fn qux(bar: Bar, baz: Baz) { | 350 | fn qux(bar: Bar, baz: Baz) { |
242 | f(); | 351 | f(); |
@@ -286,7 +395,7 @@ mod foo { | |||
286 | } | 395 | } |
287 | } | 396 | } |
288 | 397 | ||
289 | use foo::{bar::{Baz, Bar, f}, baz::*}; | 398 | use foo::{bar::{f, Baz, Bar}, baz::*}; |
290 | 399 | ||
291 | fn qux(bar: Bar, baz: Baz) { | 400 | fn qux(bar: Bar, baz: Baz) { |
292 | f(); | 401 | f(); |
@@ -494,6 +603,82 @@ fn qux(bar: Bar, baz: Baz) { | |||
494 | } | 603 | } |
495 | ", | 604 | ", |
496 | ); | 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 | |||
674 | fn qux(bar: Bar, baz: Baz) { | ||
675 | f(); | ||
676 | g(); | ||
677 | h(); | ||
678 | j(); | ||
679 | } | ||
680 | ", | ||
681 | ); | ||
497 | } | 682 | } |
498 | 683 | ||
499 | #[test] | 684 | #[test] |
diff --git a/crates/ide_db/src/defs.rs b/crates/ide_db/src/defs.rs index 0d0affc27..1e7a1f159 100644 --- a/crates/ide_db/src/defs.rs +++ b/crates/ide_db/src/defs.rs | |||
@@ -225,7 +225,7 @@ pub fn classify_name(sema: &Semantics<RootDatabase>, name: &ast::Name) -> Option | |||
225 | } | 225 | } |
226 | } | 226 | } |
227 | 227 | ||
228 | #[derive(Debug)] | 228 | #[derive(Debug, Clone, PartialEq, Eq)] |
229 | pub enum NameRefClass { | 229 | pub enum NameRefClass { |
230 | ExternCrate(Crate), | 230 | ExternCrate(Crate), |
231 | Definition(Definition), | 231 | Definition(Definition), |