crap4rust/
test_module_registry.rs1use std::collections::HashSet;
6use std::path::{Path, PathBuf};
7
8use syn::{File, Item, ItemMod};
9
10use crate::impl_collector::is_test_attrs;
11use crate::normalize_path::normalize_path;
12
13pub struct TestModuleRegistry {
14 excluded_files: HashSet<String>,
15}
16
17impl TestModuleRegistry {
18 #[must_use]
19 pub fn build(files: &[(PathBuf, File)]) -> Self {
20 let mut excluded_files = HashSet::new();
21 for (file_path, syntax) in files {
22 let base_dir = Self::own_base_dir(file_path);
23 Self::collect_excluded_mods(&syntax.items, &base_dir, &mut excluded_files);
24 }
25 Self { excluded_files }
26 }
27
28 #[must_use]
29 pub fn is_excluded(&self, file_path: &Path) -> bool {
30 self.excluded_files.contains(&normalize_path(file_path))
31 }
32
33 fn own_base_dir(file_path: &Path) -> PathBuf {
34 let parent = file_path
35 .parent()
36 .map(Path::to_path_buf)
37 .unwrap_or_default();
38 let stem = file_path
39 .file_stem()
40 .and_then(|stem| stem.to_str())
41 .unwrap_or_default();
42 if matches!(stem, "lib" | "main" | "mod") {
43 parent
44 } else {
45 parent.join(stem)
46 }
47 }
48
49 fn collect_excluded_mods(
50 items: &[Item],
51 base_dir: &Path,
52 excluded_files: &mut HashSet<String>,
53 ) {
54 for item in items {
55 let Item::Mod(item_mod) = item else {
56 continue;
57 };
58 match &item_mod.content {
59 Some((_, nested_items)) => {
60 let nested_dir = base_dir.join(item_mod.ident.to_string());
61 Self::collect_excluded_mods(nested_items, &nested_dir, excluded_files);
62 }
63 None => Self::register_if_test_gated(item_mod, base_dir, excluded_files),
64 }
65 }
66 }
67
68 fn register_if_test_gated(
69 item_mod: &ItemMod,
70 base_dir: &Path,
71 excluded_files: &mut HashSet<String>,
72 ) {
73 if !is_test_attrs(&item_mod.attrs) {
74 return;
75 }
76 if let Some(target) = Self::resolve_file_module(base_dir, item_mod) {
77 excluded_files.insert(normalize_path(&target));
78 }
79 }
80
81 fn resolve_file_module(base_dir: &Path, item_mod: &ItemMod) -> Option<PathBuf> {
82 let name = item_mod.ident.to_string();
83
84 let sibling_file = base_dir.join(format!("{name}.rs"));
85 if sibling_file.exists() {
86 return Some(sibling_file);
87 }
88
89 let directory_file = base_dir.join(&name).join("mod.rs");
90 if directory_file.exists() {
91 return Some(directory_file);
92 }
93
94 None
95 }
96}