use std::collections::HashSet;
use syn::spanned::Spanned;
use syn::visit::Visit;
use crate::adapters::shared::file_visitor::{visit_all_files, FileVisitor};
#[derive(Debug, Clone)]
pub struct WildcardImportWarning {
pub file: String,
pub line: usize,
pub module_path: String,
pub suppressed: bool,
}
pub fn detect_wildcard_imports(
parsed: &[(String, String, syn::File)],
) -> Vec<WildcardImportWarning> {
let cfg_test_files =
crate::adapters::shared::cfg_test_files::collect_cfg_test_file_paths(parsed);
let mut collector = WildcardCollector {
cfg_test_files,
file: String::new(),
warnings: Vec::new(),
in_test: false,
file_is_test: false,
};
visit_all_files(parsed, &mut collector);
collector.warnings
}
struct WildcardCollector {
cfg_test_files: HashSet<String>,
file: String,
warnings: Vec<WildcardImportWarning>,
in_test: bool,
file_is_test: bool,
}
impl FileVisitor for WildcardCollector {
fn reset_for_file(&mut self, file_path: &str) {
self.file_is_test = self.cfg_test_files.contains(file_path);
self.file = file_path.replace('\\', "/");
self.in_test = false;
}
}
impl WildcardCollector {
fn skip_glob(&self, prefix: &[String]) -> bool {
(self.in_test && prefix == ["super"])
|| self.file_is_test
|| prefix.iter().any(|p| p == "prelude")
}
fn push_glob_warning(&mut self, prefix: &[String], line: usize) {
let module_path = if prefix.is_empty() {
"*".to_string()
} else {
format!("{}::*", prefix.join("::"))
};
self.warnings.push(WildcardImportWarning {
file: self.file.clone(),
line,
module_path,
suppressed: false,
});
}
}
impl<'ast> Visit<'ast> for WildcardCollector {
fn visit_item_use(&mut self, node: &'ast syn::ItemUse) {
if !matches!(node.vis, syn::Visibility::Inherited) {
return;
}
let mut stack: Vec<(Vec<String>, &syn::UseTree)> = vec![(vec![], &node.tree)];
while let Some((prefix, tree)) = stack.pop() {
match tree {
syn::UseTree::Path(p) => {
let mut new_prefix = prefix;
new_prefix.push(p.ident.to_string());
stack.push((new_prefix, &p.tree));
}
syn::UseTree::Glob(_) if !self.skip_glob(&prefix) => {
self.push_glob_warning(&prefix, node.span().start().line);
}
syn::UseTree::Group(g) => {
for item in &g.items {
stack.push((prefix.clone(), item));
}
}
_ => {}
}
}
}
fn visit_item_mod(&mut self, node: &'ast syn::ItemMod) {
let prev = self.in_test;
if super::has_cfg_test(&node.attrs) {
self.in_test = true;
}
syn::visit::visit_item_mod(self, node);
self.in_test = prev;
}
}