use std::path::Path;
use serde_json::Value;
use xuanji::{Outcome, Violation};
use crate::collect::collect_item_dyn_exposures;
use crate::containment::matches_forbidden;
use crate::crate_scope::{dependency_names, extern_resolution, resolve_principal};
use crate::driver::run_boundaries;
use crate::dsl::DynTraitBoundary;
use crate::emit::{SingleModuleViolationContext, push_single_module_violations};
use crate::file_scope::resolve_crate;
use crate::finding::shape_finding;
use crate::module_resolve::resolve_module_items;
use crate::resolve::{canonical_path_str, collect_uses};
use crate::rules::DYN_TRAIT_RULE;
pub fn check_dyn_trait(boundaries: &[DynTraitBoundary], manifest_path: &Path) -> Outcome {
run_boundaries(boundaries, manifest_path, check_dyn_trait_boundary)
}
pub(crate) fn check_dyn_trait_boundary(
metadata: &Value,
boundary: &DynTraitBoundary,
violations: &mut Vec<Violation>,
) -> Result<(), String> {
let (package, root_file, src_dir) = resolve_crate(metadata, &boundary.crate_package)?;
let src_dir = src_dir.as_path();
let findings = if boundary.forbidden_operands.is_empty() {
dyn_module_findings(
src_dir,
&root_file,
&boundary.module,
&boundary.crate_package,
)?
} else {
dyn_operand_module_findings(
src_dir,
&root_file,
&boundary.module,
&boundary.forbidden_operands,
&boundary.crate_package,
&dependency_names(package),
)?
};
push_single_module_violations(
violations,
SingleModuleViolationContext {
src_dir,
root_file: &root_file,
module: &boundary.module,
crate_package: &boundary.crate_package,
rule: DYN_TRAIT_RULE,
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
},
findings,
)
}
pub(crate) fn dyn_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
crate_package: &str,
) -> Result<Vec<String>, String> {
let items = resolve_module_items(src_dir, root_file, module, crate_package)?;
let uses = collect_uses(&items);
let mut exposures = Vec::new();
for (ordinal, item) in items.iter().enumerate() {
collect_item_dyn_exposures(item, module, &uses, ordinal, &mut exposures);
}
let mut findings: Vec<String> = exposures.into_iter().map(shape_finding).collect();
findings.sort();
findings.dedup();
Ok(findings)
}
pub(crate) fn dyn_operand_module_findings(
src_dir: &Path,
root_file: &Path,
module: &str,
forbidden: &[String],
crate_package: &str,
dep_names: &[String],
) -> Result<Vec<String>, String> {
let items = resolve_module_items(src_dir, root_file, module, crate_package)?;
let uses = collect_uses(&items);
let resolution = extern_resolution(src_dir, root_file, crate_package, dep_names, &items)?;
let forbidden: Vec<String> = forbidden.iter().map(|f| canonical_path_str(f)).collect();
let mut exposures = Vec::new();
for (ordinal, item) in items.iter().enumerate() {
collect_item_dyn_exposures(item, module, &uses, ordinal, &mut exposures);
}
let mut findings: Vec<String> = exposures
.into_iter()
.filter(|exposure| {
forbidden.is_empty()
|| exposure.principals.iter().any(|path| {
resolve_principal(path, &uses, module, &resolution)
.is_some_and(|canonical| matches_forbidden(&canonical, &forbidden))
})
})
.map(shape_finding)
.collect();
findings.sort();
findings.dedup();
Ok(findings)
}