]> git.lizzy.rs Git - rust.git/blob - crates/assists/src/handlers/expand_glob_import.rs
0e80cde77db87151288f4538982735849123037c
[rust.git] / crates / assists / src / handlers / expand_glob_import.rs
1 use either::Either;
2 use hir::{MacroDef, Module, ModuleDef, Name, PathResolution, ScopeDef, SemanticsScope};
3 use ide_db::{
4     defs::{classify_name_ref, Definition, NameRefClass},
5     search::SearchScope,
6 };
7 use syntax::{algo, ast, AstNode, Direction, SyntaxNode, SyntaxToken, T};
8
9 use 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 // ```
39 pub(crate) fn expand_glob_import(acc: &mut Assists, ctx: &AssistContext) -> Option<()> {
40     let star = ctx.find_token_at_offset(T![*])?;
41     let (parent, mod_path) = find_parent_and_path(&star)?;
42     let target_module = match ctx.sema.resolve_path(&mod_path)? {
43         PathResolution::Def(ModuleDef::Module(it)) => it,
44         _ => return None,
45     };
46
47     let current_scope = ctx.sema.scope(&star.parent());
48     let current_module = current_scope.module()?;
49
50     let refs_in_target = find_refs_in_mod(ctx, target_module, Some(current_module))?;
51     let imported_defs = find_imported_defs(ctx, star)?;
52     let names_to_import = find_names_to_import(ctx, current_scope, refs_in_target, imported_defs);
53
54     let target = parent.clone().either(|n| n.syntax().clone(), |n| n.syntax().clone());
55     acc.add(
56         AssistId("expand_glob_import", AssistKind::RefactorRewrite),
57         "Expand glob import",
58         target.text_range(),
59         |builder| {
60             replace_ast(builder, parent, mod_path, names_to_import);
61         },
62     )
63 }
64
65 fn find_parent_and_path(
66     star: &SyntaxToken,
67 ) -> Option<(Either<ast::UseTree, ast::UseTreeList>, ast::Path)> {
68     return star.ancestors().find_map(|n| {
69         find_use_tree_list(n.clone())
70             .and_then(|(u, p)| Some((Either::Right(u), p)))
71             .or_else(|| find_use_tree(n).and_then(|(u, p)| Some((Either::Left(u), p))))
72     });
73
74     fn find_use_tree_list(n: SyntaxNode) -> Option<(ast::UseTreeList, ast::Path)> {
75         let use_tree_list = ast::UseTreeList::cast(n)?;
76         let path = use_tree_list.parent_use_tree().path()?;
77         Some((use_tree_list, path))
78     }
79
80     fn find_use_tree(n: SyntaxNode) -> Option<(ast::UseTree, ast::Path)> {
81         let use_tree = ast::UseTree::cast(n)?;
82         let path = use_tree.path()?;
83         Some((use_tree, path))
84     }
85 }
86
87 #[derive(Debug, PartialEq, Clone)]
88 enum Def {
89     ModuleDef(ModuleDef),
90     MacroDef(MacroDef),
91 }
92
93 #[derive(Debug, Clone)]
94 struct Ref {
95     // could be alias
96     visible_name: Name,
97     def: Def,
98 }
99
100 impl Ref {
101     fn from_scope_def(name: Name, scope_def: ScopeDef) -> Option<Self> {
102         match scope_def {
103             ScopeDef::ModuleDef(def) => Some(Ref { visible_name: name, def: Def::ModuleDef(def) }),
104             ScopeDef::MacroDef(def) => Some(Ref { visible_name: name, def: Def::MacroDef(def) }),
105             _ => None,
106         }
107     }
108
109     fn is_referenced_in(&self, ctx: &AssistContext, scope: &SemanticsScope) -> bool {
110         let def = match self.def {
111             Def::ModuleDef(def) => Definition::ModuleDef(def),
112             Def::MacroDef(def) => Definition::Macro(def),
113         };
114
115         if let Definition::ModuleDef(ModuleDef::Trait(tr)) = def {
116             if scope
117                 .traits_in_scope()
118                 .into_iter()
119                 .find(|other_tr_id| tr == other_tr_id.to_owned().into())
120                 .is_some()
121             {
122                 return true;
123             }
124         }
125
126         let search_scope = SearchScope::single_file(ctx.frange.file_id);
127         !def.find_usages(&ctx.sema, Some(search_scope)).is_empty()
128     }
129 }
130
131 #[derive(Debug, Clone)]
132 struct Refs(Vec<Ref>);
133
134 impl Refs {
135     fn used_refs(&self, ctx: &AssistContext, scope: &SemanticsScope) -> Refs {
136         Refs(self.0.clone().into_iter().filter(|r| r.is_referenced_in(ctx, scope)).collect())
137     }
138
139     fn filter_out_by_defs(&self, defs: Vec<Def>) -> Refs {
140         Refs(self.0.clone().into_iter().filter(|r| !defs.contains(&r.def)).collect())
141     }
142 }
143
144 fn find_refs_in_mod(
145     ctx: &AssistContext,
146     module: Module,
147     visible_from: Option<Module>,
148 ) -> Option<Refs> {
149     let module_scope = module.scope(ctx.db(), visible_from);
150     let refs = module_scope.into_iter().filter_map(|(n, d)| Ref::from_scope_def(n, d)).collect();
151     Some(Refs(refs))
152 }
153
154 // looks for name refs in parent use block's siblings
155 //
156 // mod bar {
157 //     mod qux {
158 //         struct Qux;
159 //     }
160 //
161 //     pub use qux::Qux;
162 // }
163 //
164 // ↓ ---------------
165 // use foo::*<|>;
166 // use baz::Baz;
167 // ↑ ---------------
168 fn find_imported_defs(ctx: &AssistContext, star: SyntaxToken) -> Option<Vec<Def>> {
169     let parent_use_item_syntax =
170         star.ancestors().find_map(|n| if ast::Use::can_cast(n.kind()) { Some(n) } else { None })?;
171
172     Some(
173         [Direction::Prev, Direction::Next]
174             .iter()
175             .map(|dir| {
176                 parent_use_item_syntax
177                     .siblings(dir.to_owned())
178                     .filter(|n| ast::Use::can_cast(n.kind()))
179             })
180             .flatten()
181             .filter_map(|n| Some(n.descendants().filter_map(ast::NameRef::cast)))
182             .flatten()
183             .filter_map(|r| match classify_name_ref(&ctx.sema, &r)? {
184                 NameRefClass::Definition(Definition::ModuleDef(def)) => Some(Def::ModuleDef(def)),
185                 NameRefClass::Definition(Definition::Macro(def)) => Some(Def::MacroDef(def)),
186                 _ => None,
187             })
188             .collect(),
189     )
190 }
191
192 fn find_names_to_import(
193     ctx: &AssistContext,
194     current_scope: SemanticsScope,
195     refs_in_target: Refs,
196     imported_defs: Vec<Def>,
197 ) -> Vec<Name> {
198     let used_refs = refs_in_target.used_refs(ctx, &current_scope).filter_out_by_defs(imported_defs);
199     used_refs.0.iter().map(|r| r.visible_name.clone()).collect()
200 }
201
202 fn replace_ast(
203     builder: &mut AssistBuilder,
204     parent: Either<ast::UseTree, ast::UseTreeList>,
205     path: ast::Path,
206     names_to_import: Vec<Name>,
207 ) {
208     let existing_use_trees = match parent.clone() {
209         Either::Left(_) => vec![],
210         Either::Right(u) => u
211             .use_trees()
212             .filter(|n|
213             // filter out star
214             n.star_token().is_none())
215             .collect(),
216     };
217
218     let new_use_trees: Vec<ast::UseTree> = names_to_import
219         .iter()
220         .map(|n| ast::make::use_tree(ast::make::path_from_text(&n.to_string()), None, None, false))
221         .collect();
222
223     let use_trees = [&existing_use_trees[..], &new_use_trees[..]].concat();
224
225     match use_trees.as_slice() {
226         [name] => {
227             if let Some(end_path) = name.path() {
228                 let replacement = ast::make::use_tree(
229                     ast::make::path_from_text(&format!("{}::{}", path, end_path)),
230                     None,
231                     None,
232                     false,
233                 );
234
235                 algo::diff(
236                     &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
237                     replacement.syntax(),
238                 )
239                 .into_text_edit(builder.text_edit_builder());
240             }
241         }
242         names => {
243             let replacement = match parent {
244                 Either::Left(_) => ast::make::use_tree(
245                     path,
246                     Some(ast::make::use_tree_list(names.to_owned())),
247                     None,
248                     false,
249                 )
250                 .syntax()
251                 .clone(),
252                 Either::Right(_) => ast::make::use_tree_list(names.to_owned()).syntax().clone(),
253             };
254
255             algo::diff(
256                 &parent.either(|n| n.syntax().clone(), |n| n.syntax().clone()),
257                 &replacement,
258             )
259             .into_text_edit(builder.text_edit_builder());
260         }
261     };
262 }
263
264 #[cfg(test)]
265 mod tests {
266     use crate::tests::{check_assist, check_assist_not_applicable};
267
268     use super::*;
269
270     #[test]
271     fn expanding_glob_import() {
272         check_assist(
273             expand_glob_import,
274             r"
275 mod foo {
276     pub struct Bar;
277     pub struct Baz;
278     pub struct Qux;
279
280     pub fn f() {}
281 }
282
283 use foo::*<|>;
284
285 fn qux(bar: Bar, baz: Baz) {
286     f();
287 }
288 ",
289             r"
290 mod foo {
291     pub struct Bar;
292     pub struct Baz;
293     pub struct Qux;
294
295     pub fn f() {}
296 }
297
298 use foo::{Baz, Bar, f};
299
300 fn qux(bar: Bar, baz: Baz) {
301     f();
302 }
303 ",
304         )
305     }
306
307     #[test]
308     fn expanding_glob_import_with_existing_explicit_names() {
309         check_assist(
310             expand_glob_import,
311             r"
312 mod foo {
313     pub struct Bar;
314     pub struct Baz;
315     pub struct Qux;
316
317     pub fn f() {}
318 }
319
320 use foo::{*<|>, f};
321
322 fn qux(bar: Bar, baz: Baz) {
323     f();
324 }
325 ",
326             r"
327 mod foo {
328     pub struct Bar;
329     pub struct Baz;
330     pub struct Qux;
331
332     pub fn f() {}
333 }
334
335 use foo::{f, Baz, Bar};
336
337 fn qux(bar: Bar, baz: Baz) {
338     f();
339 }
340 ",
341         )
342     }
343
344     #[test]
345     fn expanding_glob_import_with_existing_uses_in_same_module() {
346         check_assist(
347             expand_glob_import,
348             r"
349 mod foo {
350     pub struct Bar;
351     pub struct Baz;
352     pub struct Qux;
353
354     pub fn f() {}
355 }
356
357 use foo::Bar;
358 use foo::{*<|>, f};
359
360 fn qux(bar: Bar, baz: Baz) {
361     f();
362 }
363 ",
364             r"
365 mod foo {
366     pub struct Bar;
367     pub struct Baz;
368     pub struct Qux;
369
370     pub fn f() {}
371 }
372
373 use foo::Bar;
374 use foo::{f, Baz};
375
376 fn qux(bar: Bar, baz: Baz) {
377     f();
378 }
379 ",
380         )
381     }
382
383     #[test]
384     fn expanding_nested_glob_import() {
385         check_assist(
386             expand_glob_import,
387             r"
388 mod foo {
389     pub mod bar {
390         pub struct Bar;
391         pub struct Baz;
392         pub struct Qux;
393
394         pub fn f() {}
395     }
396
397     pub mod baz {
398         pub fn g() {}
399     }
400 }
401
402 use foo::{bar::{*<|>, f}, baz::*};
403
404 fn qux(bar: Bar, baz: Baz) {
405     f();
406     g();
407 }
408 ",
409             r"
410 mod foo {
411     pub mod bar {
412         pub struct Bar;
413         pub struct Baz;
414         pub struct Qux;
415
416         pub fn f() {}
417     }
418
419     pub mod baz {
420         pub fn g() {}
421     }
422 }
423
424 use foo::{bar::{f, Baz, Bar}, baz::*};
425
426 fn qux(bar: Bar, baz: Baz) {
427     f();
428     g();
429 }
430 ",
431         );
432
433         check_assist(
434             expand_glob_import,
435             r"
436 mod foo {
437     pub mod bar {
438         pub struct Bar;
439         pub struct Baz;
440         pub struct Qux;
441
442         pub fn f() {}
443     }
444
445     pub mod baz {
446         pub fn g() {}
447     }
448 }
449
450 use foo::{bar::{Bar, Baz, f}, baz::*<|>};
451
452 fn qux(bar: Bar, baz: Baz) {
453     f();
454     g();
455 }
456 ",
457             r"
458 mod foo {
459     pub mod bar {
460         pub struct Bar;
461         pub struct Baz;
462         pub struct Qux;
463
464         pub fn f() {}
465     }
466
467     pub mod baz {
468         pub fn g() {}
469     }
470 }
471
472 use foo::{bar::{Bar, Baz, f}, baz::g};
473
474 fn qux(bar: Bar, baz: Baz) {
475     f();
476     g();
477 }
478 ",
479         );
480
481         check_assist(
482             expand_glob_import,
483             r"
484 mod foo {
485     pub mod bar {
486         pub struct Bar;
487         pub struct Baz;
488         pub struct Qux;
489
490         pub fn f() {}
491     }
492
493     pub mod baz {
494         pub fn g() {}
495
496         pub mod qux {
497             pub fn h() {}
498             pub fn m() {}
499
500             pub mod q {
501                 pub fn j() {}
502             }
503         }
504     }
505 }
506
507 use foo::{
508     bar::{*, f},
509     baz::{g, qux::*<|>}
510 };
511
512 fn qux(bar: Bar, baz: Baz) {
513     f();
514     g();
515     h();
516     q::j();
517 }
518 ",
519             r"
520 mod foo {
521     pub mod bar {
522         pub struct Bar;
523         pub struct Baz;
524         pub struct Qux;
525
526         pub fn f() {}
527     }
528
529     pub mod baz {
530         pub fn g() {}
531
532         pub mod qux {
533             pub fn h() {}
534             pub fn m() {}
535
536             pub mod q {
537                 pub fn j() {}
538             }
539         }
540     }
541 }
542
543 use foo::{
544     bar::{*, f},
545     baz::{g, qux::{q, h}}
546 };
547
548 fn qux(bar: Bar, baz: Baz) {
549     f();
550     g();
551     h();
552     q::j();
553 }
554 ",
555         );
556
557         check_assist(
558             expand_glob_import,
559             r"
560 mod foo {
561     pub mod bar {
562         pub struct Bar;
563         pub struct Baz;
564         pub struct Qux;
565
566         pub fn f() {}
567     }
568
569     pub mod baz {
570         pub fn g() {}
571
572         pub mod qux {
573             pub fn h() {}
574             pub fn m() {}
575
576             pub mod q {
577                 pub fn j() {}
578             }
579         }
580     }
581 }
582
583 use foo::{
584     bar::{*, f},
585     baz::{g, qux::{h, q::*<|>}}
586 };
587
588 fn qux(bar: Bar, baz: Baz) {
589     f();
590     g();
591     h();
592     j();
593 }
594 ",
595             r"
596 mod foo {
597     pub mod bar {
598         pub struct Bar;
599         pub struct Baz;
600         pub struct Qux;
601
602         pub fn f() {}
603     }
604
605     pub mod baz {
606         pub fn g() {}
607
608         pub mod qux {
609             pub fn h() {}
610             pub fn m() {}
611
612             pub mod q {
613                 pub fn j() {}
614             }
615         }
616     }
617 }
618
619 use foo::{
620     bar::{*, f},
621     baz::{g, qux::{h, q::j}}
622 };
623
624 fn qux(bar: Bar, baz: Baz) {
625     f();
626     g();
627     h();
628     j();
629 }
630 ",
631         );
632
633         check_assist(
634             expand_glob_import,
635             r"
636 mod foo {
637     pub mod bar {
638         pub struct Bar;
639         pub struct Baz;
640         pub struct Qux;
641
642         pub fn f() {}
643     }
644
645     pub mod baz {
646         pub fn g() {}
647
648         pub mod qux {
649             pub fn h() {}
650             pub fn m() {}
651
652             pub mod q {
653                 pub fn j() {}
654             }
655         }
656     }
657 }
658
659 use foo::{
660     bar::{*, f},
661     baz::{g, qux::{q::j, *<|>}}
662 };
663
664 fn qux(bar: Bar, baz: Baz) {
665     f();
666     g();
667     h();
668     j();
669 }
670 ",
671             r"
672 mod foo {
673     pub mod bar {
674         pub struct Bar;
675         pub struct Baz;
676         pub struct Qux;
677
678         pub fn f() {}
679     }
680
681     pub mod baz {
682         pub fn g() {}
683
684         pub mod qux {
685             pub fn h() {}
686             pub fn m() {}
687
688             pub mod q {
689                 pub fn j() {}
690             }
691         }
692     }
693 }
694
695 use foo::{
696     bar::{*, f},
697     baz::{g, qux::{q::j, h}}
698 };
699
700 fn qux(bar: Bar, baz: Baz) {
701     f();
702     g();
703     h();
704     j();
705 }
706 ",
707         );
708     }
709
710     #[test]
711     fn expanding_glob_import_with_macro_defs() {
712         // FIXME: this is currently fails because `Definition::find_usages` ignores macros
713         //       https://github.com/rust-analyzer/rust-analyzer/issues/3484
714         //
715         //         check_assist(
716         //             expand_glob_import,
717         //             r"
718         // //- /lib.rs crate:foo
719         // #[macro_export]
720         // macro_rules! bar {
721         //     () => ()
722         // }
723
724         // pub fn baz() {}
725
726         // //- /main.rs crate:main deps:foo
727         // use foo::*<|>;
728
729         // fn main() {
730         //     bar!();
731         //     baz();
732         // }
733         // ",
734         //             r"
735         // use foo::{bar, baz};
736
737         // fn main() {
738         //     bar!();
739         //     baz();
740         // }
741         // ",
742         //         )
743     }
744
745     #[test]
746     fn expanding_glob_import_with_trait_method_uses() {
747         check_assist(
748             expand_glob_import,
749             r"
750 //- /lib.rs crate:foo
751 pub trait Tr {
752     fn method(&self) {}
753 }
754 impl Tr for () {}
755
756 //- /main.rs crate:main deps:foo
757 use foo::*<|>;
758
759 fn main() {
760     ().method();
761 }
762 ",
763             r"
764 use foo::Tr;
765
766 fn main() {
767     ().method();
768 }
769 ",
770         )
771     }
772
773     #[test]
774     fn expanding_is_not_applicable_if_cursor_is_not_in_star_token() {
775         check_assist_not_applicable(
776             expand_glob_import,
777             r"
778     mod foo {
779         pub struct Bar;
780         pub struct Baz;
781         pub struct Qux;
782     }
783
784     use foo::Bar<|>;
785
786     fn qux(bar: Bar, baz: Baz) {}
787     ",
788         )
789     }
790 }