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