1use std::collections::hash_map::Entry;
2
3use hir::{
4 FileRange, InFile, InRealFile, Module, ModuleDef, ModuleSource, PathResolution,
5 PathResolutionPerNs,
6};
7use ide_db::text_edit::TextRange;
8use ide_db::{
9 FxHashMap, RootDatabase,
10 defs::Definition,
11 search::{FileReference, ReferenceCategory, SearchScope},
12};
13use syntax::{
14 AstNode,
15 ast::{self, Rename},
16};
17
18use crate::{AssistContext, AssistId, Assists};
19
20pub(crate) fn remove_unused_imports(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
37 let selected_el = match ctx.covering_element() {
39 syntax::NodeOrToken::Node(n) => n,
40 syntax::NodeOrToken::Token(t) => t.parent()?,
41 };
42
43 let uses_up = selected_el.ancestors().skip(1).filter_map(ast::Use::cast);
45 let uses_down = selected_el
46 .descendants()
47 .filter(|x| x.text_range().intersect(ctx.selection_trimmed()).is_some())
48 .filter_map(ast::Use::cast);
49 let uses = uses_up.chain(uses_down).collect::<Vec<_>>();
50
51 let mut search_scopes = FxHashMap::<Module, Vec<SearchScope>>::default();
53
54 let mut unused = uses
56 .into_iter()
57 .flat_map(|u| u.syntax().descendants().filter_map(ast::UseTree::cast))
58 .filter(|u| u.use_tree_list().is_none())
59 .filter_map(|u| {
60 let use_module = ctx.sema.scope(u.syntax()).map(|s| s.module())?;
63 let scope = match search_scopes.entry(use_module) {
64 Entry::Occupied(o) => o.into_mut(),
65 Entry::Vacant(v) => v.insert(module_search_scope(ctx.db(), use_module)),
66 };
67
68 let path = if let Some(path) = u.path() {
70 path
71 } else if u.star_token().is_some() {
72 match u.syntax().ancestors().skip(1).find_map(ast::UseTree::cast) {
75 Some(parent_u) if parent_u.path().is_some() => parent_u.path()?,
76 _ => return None,
77 }
78 } else {
79 return None;
80 };
81
82 let res = match ctx.sema.resolve_path_per_ns(&path) {
84 Some(x) if x.any().is_some() => x,
85 Some(_) | None => {
86 return None;
87 }
88 };
89
90 if u.star_token().is_some() {
91 let def_mod = match res.type_ns {
93 Some(PathResolution::Def(ModuleDef::Module(module))) => module,
94 _ => return None,
95 };
96
97 if !def_mod
98 .scope(ctx.db(), Some(use_module))
99 .iter()
100 .filter_map(|(_, x)| match x {
101 hir::ScopeDef::ModuleDef(d) => Some(Definition::from(*d)),
102 _ => None,
103 })
104 .any(|d| used_once_in_scope(ctx, d, u.rename(), scope))
105 {
106 Some(u)
107 } else {
108 None
109 }
110 } else {
111 is_path_per_ns_unused_in_scope(ctx, &u, scope, &res).then_some(u)
112 }
113 })
114 .peekable();
115
116 if unused.peek().is_some() {
118 acc.add(
119 AssistId::quick_fix("remove_unused_imports"),
120 "Remove all unused imports",
121 selected_el.text_range(),
122 |builder| {
123 let unused: Vec<ast::UseTree> = unused.map(|x| builder.make_mut(x)).collect();
124 for node in unused {
125 node.remove_recursive();
126 }
127 },
128 )
129 } else {
130 None
131 }
132}
133
134fn is_path_per_ns_unused_in_scope(
135 ctx: &AssistContext<'_>,
136 u: &ast::UseTree,
137 scope: &mut Vec<SearchScope>,
138 path: &PathResolutionPerNs,
139) -> bool {
140 if let Some(PathResolution::Def(ModuleDef::Trait(ref t))) = path.type_ns {
141 if is_trait_unused_in_scope(ctx, u, scope, t) {
142 let path = [path.value_ns, path.macro_ns];
143 is_path_unused_in_scope(ctx, u, scope, &path)
144 } else {
145 false
146 }
147 } else {
148 let path = [path.type_ns, path.value_ns, path.macro_ns];
149 is_path_unused_in_scope(ctx, u, scope, &path)
150 }
151}
152
153fn is_path_unused_in_scope(
154 ctx: &AssistContext<'_>,
155 u: &ast::UseTree,
156 scope: &mut Vec<SearchScope>,
157 path: &[Option<PathResolution>],
158) -> bool {
159 !path
160 .iter()
161 .filter_map(|path| *path)
162 .filter_map(|res| match res {
163 PathResolution::Def(d) => Some(Definition::from(d)),
164 _ => None,
165 })
166 .any(|def| used_once_in_scope(ctx, def, u.rename(), scope))
167}
168
169fn is_trait_unused_in_scope(
170 ctx: &AssistContext<'_>,
171 u: &ast::UseTree,
172 scope: &mut Vec<SearchScope>,
173 t: &hir::Trait,
174) -> bool {
175 !std::iter::once((Definition::Trait(*t), u.rename()))
176 .chain(t.items(ctx.db()).into_iter().map(|item| (item.into(), None)))
177 .any(|(d, rename)| used_once_in_scope(ctx, d, rename, scope))
178}
179
180fn used_once_in_scope(
181 ctx: &AssistContext<'_>,
182 def: Definition,
183 rename: Option<Rename>,
184 scopes: &Vec<SearchScope>,
185) -> bool {
186 let mut found = false;
187
188 for scope in scopes {
189 let mut search_non_import = |_, r: FileReference| {
190 if !r.category.contains(ReferenceCategory::IMPORT) {
192 found = true;
193 true
194 } else {
195 false
196 }
197 };
198 def.usages(&ctx.sema)
199 .in_scope(scope)
200 .with_rename(rename.as_ref())
201 .search(&mut search_non_import);
202 if found {
203 break;
204 }
205 }
206
207 found
208}
209
210fn module_search_scope(db: &RootDatabase, module: hir::Module) -> Vec<SearchScope> {
212 let (file_id, range) = {
213 let InFile { file_id, value } = module.definition_source(db);
214 if let Some(InRealFile { file_id, value: call_source }) = file_id.original_call_node(db) {
215 (file_id, Some(call_source.text_range()))
216 } else {
217 (
218 file_id.original_file(db),
219 match value {
220 ModuleSource::SourceFile(_) => None,
221 ModuleSource::Module(it) => Some(it.syntax().text_range()),
222 ModuleSource::BlockExpr(it) => Some(it.syntax().text_range()),
223 },
224 )
225 }
226 };
227
228 fn split_at_subrange(first: TextRange, second: TextRange) -> (TextRange, Option<TextRange>) {
229 let intersect = first.intersect(second);
230 if let Some(intersect) = intersect {
231 let start_range = TextRange::new(first.start(), intersect.start());
232
233 if intersect.end() < first.end() {
234 (start_range, Some(TextRange::new(intersect.end(), first.end())))
235 } else {
236 (start_range, None)
237 }
238 } else {
239 (first, None)
240 }
241 }
242
243 let mut scopes = Vec::new();
244 if let Some(range) = range {
245 let mut ranges = vec![range];
246
247 for child in module.children(db) {
248 let rng = match child.definition_source(db).value {
249 ModuleSource::SourceFile(_) => continue,
250 ModuleSource::Module(it) => it.syntax().text_range(),
251 ModuleSource::BlockExpr(_) => continue,
252 };
253 let mut new_ranges = Vec::new();
254 for old_range in ranges.iter_mut() {
255 let split = split_at_subrange(*old_range, rng);
256 *old_range = split.0;
257 new_ranges.extend(split.1);
258 }
259
260 ranges.append(&mut new_ranges);
261 }
262
263 for range in ranges {
264 scopes.push(SearchScope::file_range(FileRange { file_id, range }));
265 }
266 } else {
267 scopes.push(SearchScope::single_file(file_id));
268 }
269
270 scopes
271}
272
273#[cfg(test)]
274mod tests {
275 use crate::tests::{check_assist, check_assist_not_applicable};
276
277 use super::*;
278
279 #[test]
280 fn remove_unused() {
281 check_assist(
282 remove_unused_imports,
283 r#"
284struct X();
285struct Y();
286mod z {
287 $0use super::X;
288 use super::Y;$0
289}
290"#,
291 r#"
292struct X();
293struct Y();
294mod z {
295}
296"#,
297 );
298 }
299
300 #[test]
301 fn remove_unused_is_precise() {
302 check_assist(
303 remove_unused_imports,
304 r#"
305struct X();
306mod z {
307$0use super::X;$0
308
309fn w() {
310 struct X();
311 let x = X();
312}
313}
314"#,
315 r#"
316struct X();
317mod z {
318
319fn w() {
320 struct X();
321 let x = X();
322}
323}
324"#,
325 );
326 }
327
328 #[test]
329 fn trait_name_use_is_use() {
330 check_assist_not_applicable(
331 remove_unused_imports,
332 r#"
333struct X();
334trait Y {
335 fn f();
336}
337
338impl Y for X {
339 fn f() {}
340}
341mod z {
342$0use super::X;
343use super::Y;$0
344
345fn w() {
346 X::f();
347}
348}
349"#,
350 );
351 }
352
353 #[test]
354 fn trait_item_use_is_use() {
355 check_assist_not_applicable(
356 remove_unused_imports,
357 r#"
358struct X();
359trait Y {
360 fn f(self);
361}
362
363impl Y for X {
364 fn f(self) {}
365}
366mod z {
367$0use super::X;
368use super::Y;$0
369
370fn w() {
371 let x = X();
372 x.f();
373}
374}
375"#,
376 );
377 }
378
379 #[test]
380 fn renamed_trait_item_use_is_use() {
381 check_assist_not_applicable(
382 remove_unused_imports,
383 r#"
384struct X();
385trait Y {
386 fn f(self);
387}
388
389impl Y for X {
390 fn f(self) {}
391}
392mod z {
393$0use super::X;
394use super::Y as Z;$0
395
396fn w() {
397 let x = X();
398 x.f();
399}
400}
401"#,
402 );
403 }
404
405 #[test]
406 fn renamed_underscore_trait_item_use_is_use() {
407 check_assist_not_applicable(
408 remove_unused_imports,
409 r#"
410struct X();
411trait Y {
412 fn f(self);
413}
414
415impl Y for X {
416 fn f(self) {}
417}
418mod z {
419$0use super::X;
420use super::Y as _;$0
421
422fn w() {
423 let x = X();
424 x.f();
425}
426}
427"#,
428 );
429 }
430
431 #[test]
432 fn dont_remove_used() {
433 check_assist_not_applicable(
434 remove_unused_imports,
435 r#"
436struct X();
437struct Y();
438mod z {
439$0use super::X;
440use super::Y;$0
441
442fn w() {
443 let x = X();
444 let y = Y();
445}
446}
447"#,
448 );
449 }
450
451 #[test]
452 fn remove_unused_in_braces() {
453 check_assist(
454 remove_unused_imports,
455 r#"
456struct X();
457struct Y();
458mod z {
459 $0use super::{X, Y};$0
460
461 fn w() {
462 let x = X();
463 }
464}
465"#,
466 r#"
467struct X();
468struct Y();
469mod z {
470 use super::X;
471
472 fn w() {
473 let x = X();
474 }
475}
476"#,
477 );
478 }
479
480 #[test]
481 fn remove_unused_under_cursor() {
482 check_assist(
483 remove_unused_imports,
484 r#"
485struct X();
486mod z {
487 use super::X$0;
488}
489"#,
490 r#"
491struct X();
492mod z {
493}
494"#,
495 );
496 }
497
498 #[test]
499 fn remove_multi_use_block() {
500 check_assist(
501 remove_unused_imports,
502 r#"
503struct X();
504$0mod y {
505 use super::X;
506}
507mod z {
508 use super::X;
509}$0
510"#,
511 r#"
512struct X();
513mod y {
514}
515mod z {
516}
517"#,
518 );
519 }
520
521 #[test]
522 fn remove_nested() {
523 check_assist(
524 remove_unused_imports,
525 r#"
526struct X();
527mod y {
528 struct Y();
529 mod z {
530 use crate::{X, y::Y}$0;
531 fn f() {
532 let x = X();
533 }
534 }
535}
536"#,
537 r#"
538struct X();
539mod y {
540 struct Y();
541 mod z {
542 use crate::X;
543 fn f() {
544 let x = X();
545 }
546 }
547}
548"#,
549 );
550 }
551
552 #[test]
553 fn remove_nested_first_item() {
554 check_assist(
555 remove_unused_imports,
556 r#"
557struct X();
558mod y {
559 struct Y();
560 mod z {
561 use crate::{X, y::Y}$0;
562 fn f() {
563 let y = Y();
564 }
565 }
566}
567"#,
568 r#"
569struct X();
570mod y {
571 struct Y();
572 mod z {
573 use crate::y::Y;
574 fn f() {
575 let y = Y();
576 }
577 }
578}
579"#,
580 );
581 }
582
583 #[test]
584 fn remove_unused_auto_remove_brace_nested() {
585 check_assist(
586 remove_unused_imports,
587 r#"
588mod a {
589 pub struct A();
590}
591mod b {
592 struct F();
593 mod c {
594 $0use {{super::{{
595 {d::{{{{{{{S, U}}}}}}}},
596 {{{{e::{H, L, {{{R}}}}}}}},
597 F, super::a::A
598 }}}};$0
599 fn f() {
600 let f = F();
601 let l = L();
602 let a = A();
603 let s = S();
604 let h = H();
605 }
606 }
607
608 mod d {
609 pub struct S();
610 pub struct U();
611 }
612
613 mod e {
614 pub struct H();
615 pub struct L();
616 pub struct R();
617 }
618}
619"#,
620 r#"
621mod a {
622 pub struct A();
623}
624mod b {
625 struct F();
626 mod c {
627 use super::{
628 d::S,
629 e::{H, L},
630 F, super::a::A
631 };
632 fn f() {
633 let f = F();
634 let l = L();
635 let a = A();
636 let s = S();
637 let h = H();
638 }
639 }
640
641 mod d {
642 pub struct S();
643 pub struct U();
644 }
645
646 mod e {
647 pub struct H();
648 pub struct L();
649 pub struct R();
650 }
651}
652"#,
653 );
654 }
655
656 #[test]
657 fn remove_comma_after_auto_remove_brace() {
658 check_assist(
659 remove_unused_imports,
660 r#"
661mod m {
662 pub mod x {
663 pub struct A;
664 pub struct B;
665 }
666 pub mod y {
667 pub struct C;
668 }
669}
670
671$0use m::{
672 x::{A, B},
673 y::C,
674};$0
675
676fn main() {
677 B;
678}
679"#,
680 r#"
681mod m {
682 pub mod x {
683 pub struct A;
684 pub struct B;
685 }
686 pub mod y {
687 pub struct C;
688 }
689}
690
691use m::
692 x::B
693;
694
695fn main() {
696 B;
697}
698"#,
699 );
700 check_assist(
701 remove_unused_imports,
702 r#"
703mod m {
704 pub mod x {
705 pub struct A;
706 pub struct B;
707 }
708 pub mod y {
709 pub struct C;
710 pub struct D;
711 }
712 pub mod z {
713 pub struct E;
714 pub struct F;
715 }
716}
717
718$0use m::{
719 x::{A, B},
720 y::{C, D,},
721 z::{E, F},
722};$0
723
724fn main() {
725 B;
726 C;
727 F;
728}
729"#,
730 r#"
731mod m {
732 pub mod x {
733 pub struct A;
734 pub struct B;
735 }
736 pub mod y {
737 pub struct C;
738 pub struct D;
739 }
740 pub mod z {
741 pub struct E;
742 pub struct F;
743 }
744}
745
746use m::{
747 x::B,
748 y::C,
749 z::F,
750};
751
752fn main() {
753 B;
754 C;
755 F;
756}
757"#,
758 );
759 }
760
761 #[test]
762 fn remove_nested_all_unused() {
763 check_assist(
764 remove_unused_imports,
765 r#"
766struct X();
767mod y {
768 struct Y();
769 mod z {
770 use crate::{X, y::Y}$0;
771 }
772}
773"#,
774 r#"
775struct X();
776mod y {
777 struct Y();
778 mod z {
779 }
780}
781"#,
782 );
783 }
784
785 #[test]
786 fn remove_unused_glob() {
787 check_assist(
788 remove_unused_imports,
789 r#"
790struct X();
791struct Y();
792mod z {
793 use super::*$0;
794}
795"#,
796 r#"
797struct X();
798struct Y();
799mod z {
800}
801"#,
802 );
803 }
804
805 #[test]
806 fn remove_unused_braced_glob() {
807 check_assist(
808 remove_unused_imports,
809 r#"
810struct X();
811struct Y();
812mod z {
813 use super::{*}$0;
814}
815"#,
816 r#"
817struct X();
818struct Y();
819mod z {
820}
821"#,
822 );
823 }
824
825 #[test]
826 fn remove_unused_fixes_nested_self() {
827 check_assist(
828 remove_unused_imports,
829 r#"
830mod inner {
831 pub struct X();
832 pub struct Y();
833}
834
835mod z {
836 use super::inner::{self, X}$0;
837
838 fn f() {
839 let y = inner::Y();
840 }
841}
842"#,
843 r#"mod inner {
844 pub struct X();
845 pub struct Y();
846}
847
848mod z {
849 use super::inner::{self};
850
851 fn f() {
852 let y = inner::Y();
853 }
854}
855"#,
856 );
857 }
858
859 #[test]
860 fn dont_remove_used_glob() {
861 check_assist_not_applicable(
862 remove_unused_imports,
863 r#"
864struct X();
865struct Y();
866mod z {
867 use super::*$0;
868
869 fn f() {
870 let x = X();
871 }
872}
873"#,
874 );
875 }
876
877 #[test]
878 fn only_remove_from_selection() {
879 check_assist(
880 remove_unused_imports,
881 r#"
882struct X();
883struct Y();
884mod z {
885 $0use super::X;$0
886 use super::Y;
887}
888mod w {
889 use super::Y;
890}
891"#,
892 r#"
893struct X();
894struct Y();
895mod z {
896 use super::Y;
897}
898mod w {
899 use super::Y;
900}
901"#,
902 );
903 }
904
905 #[test]
906 fn test_several_files() {
907 check_assist(
908 remove_unused_imports,
909 r#"
910//- /foo.rs
911pub struct X();
912pub struct Y();
913
914//- /main.rs
915$0use foo::X;
916use foo::Y;
917$0
918mod foo;
919mod z {
920 use crate::foo::X;
921}
922"#,
923 r#"
924
925mod foo;
926mod z {
927 use crate::foo::X;
928}
929"#,
930 );
931 }
932
933 #[test]
934 fn use_in_submodule_doesnt_count() {
935 check_assist(
936 remove_unused_imports,
937 r#"
938struct X();
939mod z {
940 use super::X$0;
941
942 mod w {
943 use crate::X;
944
945 fn f() {
946 let x = X();
947 }
948 }
949}
950"#,
951 r#"
952struct X();
953mod z {
954
955 mod w {
956 use crate::X;
957
958 fn f() {
959 let x = X();
960 }
961 }
962}
963"#,
964 );
965 }
966
967 #[test]
968 fn use_in_submodule_file_doesnt_count() {
969 check_assist(
970 remove_unused_imports,
971 r#"
972//- /z/foo.rs
973use crate::X;
974fn f() {
975 let x = X();
976}
977
978//- /main.rs
979pub struct X();
980
981mod z {
982 use crate::X$0;
983 mod foo;
984}
985"#,
986 r#"
987pub struct X();
988
989mod z {
990 mod foo;
991}
992"#,
993 );
994 }
995
996 #[test]
997 fn use_as_alias() {
998 check_assist_not_applicable(
999 remove_unused_imports,
1000 r#"
1001mod foo {
1002 pub struct Foo {}
1003}
1004
1005use foo::Foo as Bar$0;
1006
1007fn test(_: Bar) {}
1008"#,
1009 );
1010
1011 check_assist(
1012 remove_unused_imports,
1013 r#"
1014mod foo {
1015 pub struct Foo {}
1016 pub struct Bar {}
1017 pub struct Qux {}
1018 pub trait Quux {
1019 fn quxx(&self) {}
1020 }
1021 impl<T> Quxx for T {}
1022}
1023
1024use foo::{Foo as Bar, Bar as Baz, Qux as _, Quxx as _}$0;
1025
1026fn test(_: Bar) {
1027 let a = ();
1028 a.quxx();
1029}
1030"#,
1031 r#"
1032mod foo {
1033 pub struct Foo {}
1034 pub struct Bar {}
1035 pub struct Qux {}
1036 pub trait Quux {
1037 fn quxx(&self) {}
1038 }
1039 impl<T> Quxx for T {}
1040}
1041
1042use foo::{Foo as Bar, Quxx as _};
1043
1044fn test(_: Bar) {
1045 let a = ();
1046 a.quxx();
1047}
1048"#,
1049 );
1050 }
1051
1052 #[test]
1053 fn test_unused_macro() {
1054 check_assist(
1055 remove_unused_imports,
1056 r#"
1057//- /foo.rs crate:foo
1058#[macro_export]
1059macro_rules! m { () => {} }
1060
1061//- /main.rs crate:main deps:foo
1062use foo::m;$0
1063fn main() {}
1064"#,
1065 r#"
1066fn main() {}
1067"#,
1068 );
1069
1070 check_assist_not_applicable(
1071 remove_unused_imports,
1072 r#"
1073//- /foo.rs crate:foo
1074#[macro_export]
1075macro_rules! m { () => {} }
1076
1077//- /main.rs crate:main deps:foo
1078use foo::m;$0
1079fn main() {
1080 m!();
1081}
1082"#,
1083 );
1084
1085 check_assist_not_applicable(
1086 remove_unused_imports,
1087 r#"
1088//- /foo.rs crate:foo
1089#[macro_export]
1090macro_rules! m { () => {} }
1091
1092//- /bar.rs crate:bar deps:foo
1093pub use foo::m;
1094fn m() {}
1095
1096
1097//- /main.rs crate:main deps:bar
1098use bar::m;$0
1099fn main() {
1100 m!();
1101}
1102"#,
1103 );
1104 }
1105
1106 #[test]
1107 fn test_conflict_derive_macro() {
1108 check_assist_not_applicable(
1109 remove_unused_imports,
1110 r#"
1111//- proc_macros: derive_identity
1112//- minicore: derive
1113//- /bar.rs crate:bar
1114pub use proc_macros::DeriveIdentity;
1115pub trait DeriveIdentity {}
1116
1117//- /main.rs crate:main deps:bar
1118$0use bar::DeriveIdentity;$0
1119#[derive(DeriveIdentity)]
1120struct S;
1121"#,
1122 );
1123
1124 check_assist_not_applicable(
1125 remove_unused_imports,
1126 r#"
1127//- proc_macros: derive_identity
1128//- minicore: derive
1129//- /bar.rs crate:bar
1130pub use proc_macros::DeriveIdentity;
1131pub fn DeriveIdentity() {}
1132
1133//- /main.rs crate:main deps:bar
1134$0use bar::DeriveIdentity;$0
1135#[derive(DeriveIdentity)]
1136struct S;
1137"#,
1138 );
1139
1140 check_assist_not_applicable(
1141 remove_unused_imports,
1142 r#"
1143//- proc_macros: derive_identity
1144//- minicore: derive
1145//- /bar.rs crate:bar
1146pub use proc_macros::DeriveIdentity;
1147pub fn DeriveIdentity() {}
1148
1149//- /main.rs crate:main deps:bar
1150$0use bar::DeriveIdentity;$0
1151fn main() {
1152 DeriveIdentity();
1153}
1154"#,
1155 );
1156 }
1157}