ryo_source/ops/
remove_unused_imports.rs1use crate::ast::{RustAST, UnusedImport};
6use crate::visitor::{extract_use_names, use_tree_to_path};
7
8pub struct RemoveUnusedImports;
10
11impl RemoveUnusedImports {
12 pub fn detect(ast: &RustAST) -> Vec<UnusedImport> {
14 let used_idents = ast.collect_used_identifiers();
15 let imports = ast.collect_imports();
16
17 let mut unused = Vec::new();
18
19 for import in imports {
20 let names = extract_use_names(&import.tree);
21 let path = use_tree_to_path(&import.tree);
22
23 for name in names {
24 if !used_idents.contains(&name) {
25 unused.push(UnusedImport {
26 path: path.clone(),
27 name,
28 });
29 }
30 }
31 }
32
33 unused
34 }
35
36 pub fn apply(ast: &mut RustAST) -> Vec<UnusedImport> {
38 let used_idents = ast.collect_used_identifiers();
39 let mut removed = Vec::new();
40
41 let items_to_remove: Vec<usize> = ast
43 .file()
44 .items
45 .iter()
46 .enumerate()
47 .filter_map(|(i, item)| {
48 if let syn::Item::Use(use_item) = item {
49 let names = extract_use_names(&use_item.tree);
50 let path = use_tree_to_path(&use_item.tree);
51
52 let all_unused = names.iter().all(|name| !used_idents.contains(name));
54
55 if all_unused && !names.is_empty() {
56 for name in names {
57 removed.push(UnusedImport {
58 path: path.clone(),
59 name,
60 });
61 }
62 return Some(i);
63 }
64 }
65 None
66 })
67 .collect();
68
69 for i in items_to_remove.into_iter().rev() {
71 ast.items_mut().remove(i);
72 }
73
74 removed
75 }
76}
77
78#[cfg(test)]
79mod tests {
80 use super::*;
81
82 #[test]
83 fn test_detect_unused_simple() {
84 let ast = RustAST::parse("use std::io;\n\nfn main() {}").unwrap();
85 let unused = RemoveUnusedImports::detect(&ast);
86
87 assert_eq!(unused.len(), 1);
88 assert_eq!(unused[0].name, "io");
89 }
90
91 #[test]
92 fn test_detect_used_import() {
93 let ast = RustAST::parse(
94 r#"
95 use std::io;
96
97 fn main() {
98 let _ = io::stdin();
99 }
100 "#,
101 )
102 .unwrap();
103
104 let unused = RemoveUnusedImports::detect(&ast);
105 assert!(unused.is_empty());
106 }
107
108 #[test]
109 fn test_detect_multiple_imports() {
110 let ast = RustAST::parse(
111 r#"
112 use std::io;
113 use std::fs;
114
115 fn main() {
116 let _ = fs::read_dir(".");
117 }
118 "#,
119 )
120 .unwrap();
121
122 let unused = RemoveUnusedImports::detect(&ast);
123 assert_eq!(unused.len(), 1);
124 assert_eq!(unused[0].name, "io");
125 }
126
127 #[test]
128 fn test_remove_unused() {
129 let mut ast = RustAST::parse(
130 r#"
131 use std::io;
132 use std::fs;
133
134 fn main() {
135 let _ = fs::read_dir(".");
136 }
137 "#,
138 )
139 .unwrap();
140
141 let removed = RemoveUnusedImports::apply(&mut ast);
142 assert_eq!(removed.len(), 1);
143 assert_eq!(removed[0].name, "io");
144
145 let output = ast.to_string();
146 assert!(!output.contains("std :: io"), "should not contain std::io");
147 assert!(
148 output.contains("std :: fs") || output.contains("std::fs"),
149 "should contain std::fs: {}",
150 output
151 );
152 }
153
154 #[test]
155 fn test_renamed_import_used() {
156 let ast = RustAST::parse(
157 r#"
158 use std::io as stdio;
159
160 fn main() {
161 let _ = stdio::stdin();
162 }
163 "#,
164 )
165 .unwrap();
166
167 let unused = RemoveUnusedImports::detect(&ast);
168 assert!(unused.is_empty());
169 }
170
171 #[test]
172 fn test_renamed_import_unused() {
173 let ast = RustAST::parse(
174 r#"
175 use std::io as stdio;
176
177 fn main() {}
178 "#,
179 )
180 .unwrap();
181
182 let unused = RemoveUnusedImports::detect(&ast);
183 assert_eq!(unused.len(), 1);
184 assert_eq!(unused[0].name, "stdio");
185 }
186
187 #[test]
188 fn test_group_import_partial() {
189 let ast = RustAST::parse(
190 r#"
191 use std::{io, fs};
192
193 fn main() {
194 let _ = fs::read_dir(".");
195 }
196 "#,
197 )
198 .unwrap();
199
200 let unused = RemoveUnusedImports::detect(&ast);
201 assert_eq!(unused.len(), 1);
203 assert_eq!(unused[0].name, "io");
204 }
205}