use std::collections::HashSet;
use std::path::{Path, PathBuf};
use serde_json::Value;
use xuanji::{Outcome, Polarity, Violation};
use crate::containment::matches_allowed;
use crate::driver::run_boundaries;
use crate::dsl::TraitImplBoundary;
use crate::emit::{MultiModuleViolationContext, push_multi_module_violations};
use crate::errors::{ambiguous_trait_anchor_error, unknown_trait_error};
use crate::file_scope::{over_each_unit, resolve_crate_units};
use crate::finding::{SemanticFact, sort_attributed_facts};
use crate::resolve::{
AliasMap, BareFallback, canonical_path_str, canonical_self_owner, expand_canonical_paths,
render_last_segment_args, resolve_path_all, validate_path_operands,
};
use crate::rules::TRAIT_IMPL_RULE;
use crate::scan::scan_crate;
pub fn check_trait_impl_locality(
boundaries: &[TraitImplBoundary],
manifest_path: &Path,
) -> Outcome {
run_boundaries(boundaries, manifest_path, check_trait_impl_boundary)
}
pub(crate) fn check_trait_impl_boundary(
metadata: &Value,
boundary: &TraitImplBoundary,
violations: &mut Vec<Violation>,
) -> Result<(), String> {
let (_package, units) = resolve_crate_units(metadata, &boundary.crate_package)?;
over_each_unit(
&units,
&unknown_trait_error(&boundary.trait_path, &boundary.crate_package),
|root_file, src_dir, unit| {
let TraitImplReaction { anchor, findings } = trait_impl_findings(
src_dir,
root_file,
&boundary.trait_path,
&boundary.allowed_locations,
&boundary.crate_package,
)?;
let target = anchor;
push_multi_module_violations(
violations,
MultiModuleViolationContext {
target: &target,
rule: TRAIT_IMPL_RULE,
rule_key: boundary.rule_key_for_anchor(&target),
reason: &boundary.reason,
severity: boundary.severity,
anchor: boundary.anchor(),
polarity: Polarity::AllowlistGap,
crate_package: &boundary.crate_package,
unit,
},
findings,
);
Ok(())
},
)
}
pub(crate) struct TraitImplReaction {
pub(crate) anchor: String,
pub(crate) findings: Vec<(SemanticFact, String, PathBuf)>,
}
pub(crate) fn trait_impl_findings(
src_dir: &Path,
root_file: &Path,
trait_path: &str,
allowed: &[String],
crate_package: &str,
) -> Result<TraitImplReaction, String> {
validate_path_operands(allowed)?;
let scan = scan_crate(src_dir, root_file, crate_package, &HashSet::new())?;
let given = canonical_path_str(trait_path);
let true_anchors = expand_canonical_paths(&given, &AliasMap::new(), &scan.reexports);
let mut defining_anchors: Vec<String> = true_anchors
.iter()
.filter(|anchor| scan.trait_defs.contains(*anchor))
.cloned()
.collect();
defining_anchors.sort();
defining_anchors.dedup();
let anchor = match defining_anchors.len() {
0 => return Err(unknown_trait_error(trait_path, crate_package)),
1 => defining_anchors.remove(0),
_ => {
return Err(ambiguous_trait_anchor_error(
trait_path,
crate_package,
&defining_anchors,
));
}
};
let allowed: Vec<String> = allowed.iter().map(|a| canonical_path_str(a)).collect();
let mut findings = Vec::new();
for (ordinal, site) in scan.impls.iter().enumerate() {
let resolved_candidates = resolve_path_all(
&site.trait_path,
&site.uses,
&site.module,
BareFallback::CurrentModule,
);
if resolved_candidates.is_empty() {
continue;
}
let canonical_candidates: Vec<String> = resolved_candidates
.iter()
.flat_map(|resolved| {
expand_canonical_paths(resolved, &AliasMap::new(), &scan.reexports)
})
.collect();
let Some(canonical) = canonical_candidates
.iter()
.find(|candidate| true_anchors.contains(candidate))
else {
continue;
};
if matches_allowed(&site.module, &allowed) {
continue;
}
let owner = canonical_self_owner(
&site.self_ty,
&site.uses,
&site.module,
ordinal,
&site.type_params,
);
let trait_ref = format!(
"{canonical}{}",
render_last_segment_args(&site.trait_path).unwrap_or_else(|| format!("<_#{ordinal}>"))
);
findings.push((
SemanticFact::MisplacedImpl {
module: site.module.clone(),
trait_ref,
owner,
},
site.module.clone(),
site.file.clone(),
));
}
sort_attributed_facts(&mut findings)?;
Ok(TraitImplReaction { anchor, findings })
}