aboutsummaryrefslogtreecommitdiff
path: root/crates/assists/src/handlers/expand_glob_import.rs
diff options
context:
space:
mode:
Diffstat (limited to 'crates/assists/src/handlers/expand_glob_import.rs')
-rw-r--r--crates/assists/src/handlers/expand_glob_import.rs385
1 files changed, 385 insertions, 0 deletions
diff --git a/crates/assists/src/handlers/expand_glob_import.rs b/crates/assists/src/handlers/expand_glob_import.rs
new file mode 100644
index 000000000..81d0af2f3
--- /dev/null
+++ b/crates/assists/src/handlers/expand_glob_import.rs
@@ -0,0 +1,385 @@
1use either::Either;
2use hir::{AssocItem, MacroDef, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
3use ide_db::{
4 defs::{classify_name_ref, Definition, NameRefClass},
5 RootDatabase,
6};
7use syntax::{algo, ast, match_ast, AstNode, SyntaxNode, SyntaxToken, T};
8
9use crate::{
10 assist_context::{AssistBuilder, AssistContext, Assists},
11 AssistId, AssistKind,
12};
13
14// Assist: expand_glob_import
15//
16// Expands glob imports.
17//
18// ```
19// mod foo {
20// pub struct Bar;
21// pub struct Baz;
22// }
23//
24// use foo::*<|>;
25//
26// fn qux(bar: Bar, baz: Baz) {}
27// ```
28// ->
29// ```
30// mod foo {
31// pub struct Bar;
32// pub struct Baz;
33// }
34//
35// use foo::{Baz, Bar};
36//
37// fn qux(bar: Bar, baz: Baz) {}
38// ```
39pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40 let star = ctx.find_token_at_offset(T![*])?;
41 let mod_path = find_mod_path(&star)?;
42 let module = match ctx.sema.resolve_path(&mod_path)? {
43 PathResolution::Def(ModuleDef::Module(it)) => it,
44 _ => return None,
45 };
46
47 let source_file = ctx.source_file();
48 let scope = ctx.sema.scope_at_offset(source_file.syntax(), ctx.offset());
49
50 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);
54
55 let parent = star.parent().parent()?;
56 acc.add(
57 AssistId("expand_glob_import", AssistKind::RefactorRewrite),
58 "Expand glob import",
59 parent.text_range(),
60 |builder| {
61 replace_ast(builder, &parent, mod_path, used_names);
62 },
63 )
64}
65
66fn find_mod_path(star: &SyntaxToken) -> Option<ast::Path> {
67 star.ancestors().find_map(|n| ast::UseTree::cast(n).and_then(|u| u.path()))
68}
69
70#[derive(PartialEq)]
71enum Def {
72 ModuleDef(ModuleDef),
73 MacroDef(MacroDef),
74}
75
76impl Def {
77 fn name(&self, db: &RootDatabase) -> Option<Name> {
78 match self {
79 Def::ModuleDef(def) => def.name(db),
80 Def::MacroDef(def) => def.name(db),
81 }
82 }
83}
84
85fn find_defs_in_mod(
86 ctx: &AssistContext,
87 from: SemanticsScope<'_>,
88 module: hir::Module,
89) -> Option<Vec<Def>> {
90 let module_scope = module.scope(ctx.db(), from.module());
91
92 let mut defs = vec![];
93 for (_, def) in module_scope {
94 match def {
95 ScopeDef::ModuleDef(def) => defs.push(Def::ModuleDef(def)),
96 ScopeDef::MacroDef(def) => defs.push(Def::MacroDef(def)),
97 _ => continue,
98 }
99 }
100
101 Some(defs)
102}
103
104fn find_used_names(
105 ctx: &AssistContext,
106 defs_in_mod: Vec<Def>,
107 name_refs_in_source_file: Vec<ast::NameRef>,
108) -> Vec<Name> {
109 let defs_in_source_file = name_refs_in_source_file
110 .iter()
111 .filter_map(|r| classify_name_ref(&ctx.sema, r))
112 .filter_map(|rc| match rc {
113 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
114 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
115 _ => None,
116 })
117 .collect::<Vec<Def>>();
118
119 defs_in_mod
120 .iter()
121 .filter(|def| {
122 if let Def::ModuleDef(ModuleDef::Trait(tr)) = def {
123 for item in tr.items(ctx.db()) {
124 if let AssocItem::Function(f) = item {
125 if defs_in_source_file.contains(&Def::ModuleDef(ModuleDef::Function(f))) {
126 return true;
127 }
128 }
129 }
130 }
131
132 defs_in_source_file.contains(def)
133 })
134 .filter_map(|d| d.name(ctx.db()))
135 .collect()
136}
137
138fn replace_ast(
139 builder: &mut AssistBuilder,
140 node: &SyntaxNode,
141 path: ast::Path,
142 used_names: Vec<Name>,
143) {
144 let replacement: Either<ast::UseTree, ast::UseTreeList> = match used_names.as_slice() {
145 [name] => Either::Left(ast::make::use_tree(
146 ast::make::path_from_text(&format!("{}::{}", path, name)),
147 None,
148 None,
149 false,
150 )),
151 names => Either::Right(ast::make::use_tree_list(names.iter().map(|n| {
152 ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false)
153 }))),
154 };
155
156 let mut replace_node = |replacement: Either<ast::UseTree, ast::UseTreeList>| {
157 algo::diff(node, &replacement.either(|u| u.syntax().clone(), |ut| ut.syntax().clone()))
158 .into_text_edit(builder.text_edit_builder());
159 };
160
161 match_ast! {
162 match node {
163 ast::UseTree(use_tree) => {
164 replace_node(replacement);
165 },
166 ast::UseTreeList(use_tree_list) => {
167 replace_node(replacement);
168 },
169 ast::Use(use_item) => {
170 builder.replace_ast(use_item, ast::make::use_(replacement.left_or_else(|ut| ast::make::use_tree(path, Some(ut), None, false))));
171 },
172 _ => {},
173 }
174 }
175}
176
177#[cfg(test)]
178mod tests {
179 use crate::tests::{check_assist, check_assist_not_applicable};
180
181 use super::*;
182
183 #[test]
184 fn expanding_glob_import() {
185 check_assist(
186 expand_glob_import,
187 r"
188mod foo {
189 pub struct Bar;
190 pub struct Baz;
191 pub struct Qux;
192
193 pub fn f() {}
194}
195
196use foo::*<|>;
197
198fn qux(bar: Bar, baz: Baz) {
199 f();
200}
201",
202 r"
203mod foo {
204 pub struct Bar;
205 pub struct Baz;
206 pub struct Qux;
207
208 pub fn f() {}
209}
210
211use foo::{Baz, Bar, f};
212
213fn qux(bar: Bar, baz: Baz) {
214 f();
215}
216",
217 )
218 }
219
220 #[test]
221 fn expanding_glob_import_with_existing_explicit_names() {
222 check_assist(
223 expand_glob_import,
224 r"
225mod foo {
226 pub struct Bar;
227 pub struct Baz;
228 pub struct Qux;
229
230 pub fn f() {}
231}
232
233use foo::{*<|>, f};
234
235fn qux(bar: Bar, baz: Baz) {
236 f();
237}
238",
239 r"
240mod foo {
241 pub struct Bar;
242 pub struct Baz;
243 pub struct Qux;
244
245 pub fn f() {}
246}
247
248use foo::{Baz, Bar, f};
249
250fn qux(bar: Bar, baz: Baz) {
251 f();
252}
253",
254 )
255 }
256
257 #[test]
258 fn expanding_nested_glob_import() {
259 check_assist(
260 expand_glob_import,
261 r"
262mod foo {
263 mod bar {
264 pub struct Bar;
265 pub struct Baz;
266 pub struct Qux;
267
268 pub fn f() {}
269 }
270
271 mod baz {
272 pub fn g() {}
273 }
274}
275
276use foo::{bar::{*<|>, f}, baz::*};
277
278fn qux(bar: Bar, baz: Baz) {
279 f();
280 g();
281}
282",
283 r"
284mod foo {
285 mod bar {
286 pub struct Bar;
287 pub struct Baz;
288 pub struct Qux;
289
290 pub fn f() {}
291 }
292
293 mod baz {
294 pub fn g() {}
295 }
296}
297
298use foo::{bar::{Baz, Bar, f}, baz::*};
299
300fn qux(bar: Bar, baz: Baz) {
301 f();
302 g();
303}
304",
305 )
306 }
307
308 #[test]
309 fn expanding_glob_import_with_macro_defs() {
310 check_assist(
311 expand_glob_import,
312 r"
313//- /lib.rs crate:foo
314#[macro_export]
315macro_rules! bar {
316 () => ()
317}
318
319pub fn baz() {}
320
321//- /main.rs crate:main deps:foo
322use foo::*<|>;
323
324fn main() {
325 bar!();
326 baz();
327}
328",
329 r"
330use foo::{bar, baz};
331
332fn main() {
333 bar!();
334 baz();
335}
336",
337 )
338 }
339
340 #[test]
341 fn expanding_glob_import_with_trait_method_uses() {
342 check_assist(
343 expand_glob_import,
344 r"
345//- /lib.rs crate:foo
346pub trait Tr {
347 fn method(&self) {}
348}
349impl Tr for () {}
350
351//- /main.rs crate:main deps:foo
352use foo::*<|>;
353
354fn main() {
355 ().method();
356}
357",
358 r"
359use foo::Tr;
360
361fn main() {
362 ().method();
363}
364",
365 )
366 }
367
368 #[test]
369 fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
370 check_assist_not_applicable(
371 expand_glob_import,
372 r"
373 mod foo {
374 pub struct Bar;
375 pub struct Baz;
376 pub struct Qux;
377 }
378
379 use foo::Bar<|>;
380
381 fn qux(bar: Bar, baz: Baz) {}
382 ",
383 )
384 }
385}