mod checks;
mod rendering;
use crate::adapters::analyzers::architecture::rendering::{
build_architecture_finding, build_file_refs,
};
use crate::adapters::analyzers::architecture::{MatchLocation, ViolationKind};
use crate::domain::{Finding, Severity};
use crate::ports::AnalysisContext;
use checks::{
check_async, check_error_variants, check_object_safety, check_receiver, check_required_param,
check_return_type, check_supertraits, TraitSite,
};
use globset::GlobSet;
#[derive(Debug)]
pub struct CompiledTraitContract {
pub name: String,
pub scope: GlobSet,
pub receiver_may_be: Option<Vec<String>>,
pub required_param_type_contains: Option<String>,
pub forbidden_return_type_contains: Vec<String>,
pub forbidden_error_variant_contains: Vec<String>,
pub error_types: Vec<String>,
pub methods_must_be_async: Option<bool>,
pub must_be_object_safe: Option<bool>,
pub required_supertraits_contain: Vec<String>,
}
pub fn check_trait_contracts(
files: &[(String, &syn::File)],
rules: &[CompiledTraitContract],
) -> Vec<MatchLocation> {
files
.iter()
.flat_map(|(path, ast)| check_file(path, ast, rules))
.collect()
}
fn check_file(path: &str, ast: &syn::File, rules: &[CompiledTraitContract]) -> Vec<MatchLocation> {
let mut traits: Vec<&syn::ItemTrait> = Vec::new();
collect_traits(&ast.items, &mut traits);
rules
.iter()
.filter(|r| r.scope.is_match(path))
.flat_map(|r| {
traits
.iter()
.flat_map(move |t| check_trait(path, t, ast, r))
})
.collect()
}
fn collect_traits<'a>(items: &'a [syn::Item], out: &mut Vec<&'a syn::ItemTrait>) {
for item in items {
match item {
syn::Item::Trait(t) => out.push(t),
syn::Item::Mod(m) => {
if let Some((_, inner)) = &m.content {
collect_traits(inner, out);
}
}
_ => {}
}
}
}
fn check_trait(
path: &str,
t: &syn::ItemTrait,
ast: &syn::File,
rule: &CompiledTraitContract,
) -> Vec<MatchLocation> {
let methods = checks::trait_methods(t);
let site = TraitSite {
path,
t,
methods: &methods,
ast,
};
let mut out = Vec::new();
check_receiver(&site, rule, &mut out);
check_async(&site, rule, &mut out);
check_return_type(&site, rule, &mut out);
check_required_param(&site, rule, &mut out);
check_supertraits(&site, rule, &mut out);
check_object_safety(&site, rule, &mut out);
check_error_variants(&site, rule, &mut out);
out
}
pub fn collect_findings(
ctx: &AnalysisContext<'_>,
rules: &[CompiledTraitContract],
) -> Vec<Finding> {
if rules.is_empty() {
return Vec::new();
}
check_trait_contracts(&build_file_refs(ctx), rules)
.into_iter()
.map(hit_to_finding)
.collect()
}
fn hit_to_finding(hit: MatchLocation) -> Finding {
let rule_id = match &hit.kind {
ViolationKind::TraitContract { check, .. } => {
format!("architecture/trait_contract/{check}")
}
_ => "architecture/trait_contract".to_string(),
};
build_architecture_finding(hit, rule_id, "trait contract", Severity::High)
}