use crate::ast::{RustAST, UnusedImport};
use crate::visitor::{extract_use_names, use_tree_to_path};
pub struct RemoveUnusedImports;
impl RemoveUnusedImports {
pub fn detect(ast: &RustAST) -> Vec<UnusedImport> {
let used_idents = ast.collect_used_identifiers();
let imports = ast.collect_imports();
let mut unused = Vec::new();
for import in imports {
let names = extract_use_names(&import.tree);
let path = use_tree_to_path(&import.tree);
for name in names {
if !used_idents.contains(&name) {
unused.push(UnusedImport {
path: path.clone(),
name,
});
}
}
}
unused
}
pub fn apply(ast: &mut RustAST) -> Vec<UnusedImport> {
let used_idents = ast.collect_used_identifiers();
let mut removed = Vec::new();
let items_to_remove: Vec<usize> = ast
.file()
.items
.iter()
.enumerate()
.filter_map(|(i, item)| {
if let syn::Item::Use(use_item) = item {
let names = extract_use_names(&use_item.tree);
let path = use_tree_to_path(&use_item.tree);
let all_unused = names.iter().all(|name| !used_idents.contains(name));
if all_unused && !names.is_empty() {
for name in names {
removed.push(UnusedImport {
path: path.clone(),
name,
});
}
return Some(i);
}
}
None
})
.collect();
for i in items_to_remove.into_iter().rev() {
ast.items_mut().remove(i);
}
removed
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detect_unused_simple() {
let ast = RustAST::parse("use std::io;\n\nfn main() {}").unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert_eq!(unused.len(), 1);
assert_eq!(unused[0].name, "io");
}
#[test]
fn test_detect_used_import() {
let ast = RustAST::parse(
r#"
use std::io;
fn main() {
let _ = io::stdin();
}
"#,
)
.unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert!(unused.is_empty());
}
#[test]
fn test_detect_multiple_imports() {
let ast = RustAST::parse(
r#"
use std::io;
use std::fs;
fn main() {
let _ = fs::read_dir(".");
}
"#,
)
.unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert_eq!(unused.len(), 1);
assert_eq!(unused[0].name, "io");
}
#[test]
fn test_remove_unused() {
let mut ast = RustAST::parse(
r#"
use std::io;
use std::fs;
fn main() {
let _ = fs::read_dir(".");
}
"#,
)
.unwrap();
let removed = RemoveUnusedImports::apply(&mut ast);
assert_eq!(removed.len(), 1);
assert_eq!(removed[0].name, "io");
let output = ast.to_string();
assert!(!output.contains("std :: io"), "should not contain std::io");
assert!(
output.contains("std :: fs") || output.contains("std::fs"),
"should contain std::fs: {}",
output
);
}
#[test]
fn test_renamed_import_used() {
let ast = RustAST::parse(
r#"
use std::io as stdio;
fn main() {
let _ = stdio::stdin();
}
"#,
)
.unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert!(unused.is_empty());
}
#[test]
fn test_renamed_import_unused() {
let ast = RustAST::parse(
r#"
use std::io as stdio;
fn main() {}
"#,
)
.unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert_eq!(unused.len(), 1);
assert_eq!(unused[0].name, "stdio");
}
#[test]
fn test_group_import_partial() {
let ast = RustAST::parse(
r#"
use std::{io, fs};
fn main() {
let _ = fs::read_dir(".");
}
"#,
)
.unwrap();
let unused = RemoveUnusedImports::detect(&ast);
assert_eq!(unused.len(), 1);
assert_eq!(unused[0].name, "io");
}
}