use std::collections::BTreeSet;
use std::path::Path;
use crate::finding::model::package_tree::PackageTree;
use crate::finding::parsing::module_declaration_finder::ModuleDeclarationFinder;
use crate::reporting::offence::Offence;
use crate::rule::Rule;
use crate::source_file::SourceFile;
pub struct RegistryCompletenessRule;
impl RegistryCompletenessRule {
pub fn new() -> Self {
Self
}
fn undeclared_in(
&self,
tree: &PackageTree,
files: &[SourceFile],
directory: &Path,
) -> Vec<Offence> {
let registries = tree.registries_in(directory);
let Some(primary) = registries.first() else {
return Vec::new();
};
let Some(declared) = Self::declared_by(files, ®istries) else {
return Vec::new();
};
tree.expected_modules_in(directory)
.into_iter()
.filter(|name| !declared.contains(name))
.map(|name| self.offence(primary, &name))
.collect()
}
fn declared_by(files: &[SourceFile], registries: &[&Path]) -> Option<BTreeSet<String>> {
let mut declared = BTreeSet::new();
for path in registries {
let file = Self::file_at(files, path)?;
declared.extend(ModuleDeclarationFinder::find(file)?);
}
Some(declared)
}
fn file_at<'a>(files: &'a [SourceFile], path: &Path) -> Option<&'a SourceFile> {
let wanted = path.to_string_lossy().replace('\\', "/");
files
.iter()
.find(|file| file.relative_path().replace('\\', "/") == wanted)
}
fn offence(&self, registry: &Path, name: &str) -> Offence {
let registry = registry.to_string_lossy().replace('\\', "/");
Offence::new(
®istry,
1,
self.name(),
format!("`{name}` is not declared here, so its file is never compiled"),
format!("add `pub mod {name};` to {registry}"),
)
.with_subject(name)
}
}
impl Default for RegistryCompletenessRule {
fn default() -> Self {
Self::new()
}
}
impl Rule for RegistryCompletenessRule {
fn name(&self) -> &'static str {
"registry-completeness"
}
fn check(&self, _file: &SourceFile) -> Vec<Offence> {
Vec::new()
}
fn check_workspace(&self, files: &[SourceFile]) -> Vec<Offence> {
let tree = PackageTree::of(files);
tree.directories()
.iter()
.flat_map(|directory| self.undeclared_in(&tree, files, directory))
.collect()
}
fn requirement(&self) -> Option<&'static str> {
None
}
fn is_configured(&self) -> bool {
true
}
}