use super::pub_fns::PubFnInfo;
use super::workspace_graph::{canonical_name_for_pub_fn, CallGraph, WalkState};
use crate::adapters::analyzers::architecture::compiled::CompiledCallParity;
use crate::adapters::analyzers::architecture::layer_rule::LayerDefinitions;
use crate::adapters::analyzers::architecture::{MatchLocation, ViolationKind};
use std::collections::HashMap;
pub(crate) fn check_no_delegation<'ast>(
pub_fns_by_layer: &HashMap<String, Vec<PubFnInfo<'ast>>>,
graph: &CallGraph,
layers: &LayerDefinitions,
cp: &CompiledCallParity,
) -> Vec<MatchLocation> {
let mut out = Vec::new();
for adapter_layer in &cp.adapters {
let Some(fns) = pub_fns_by_layer.get(adapter_layer) else {
continue;
};
for info in fns {
if fn_reaches_target(info, graph, layers, &cp.target, cp.call_depth) {
continue;
}
out.push(MatchLocation {
file: info.file.clone(),
line: info.line,
column: 0,
kind: ViolationKind::CallParityNoDelegation {
fn_name: info.fn_name.clone(),
adapter_layer: adapter_layer.clone(),
target_layer: cp.target.clone(),
call_depth: cp.call_depth,
},
});
}
}
out
}
fn fn_reaches_target(
info: &PubFnInfo<'_>,
graph: &CallGraph,
layers: &LayerDefinitions,
target_layer: &str,
call_depth: usize,
) -> bool {
let start = canonical_name_for_pub_fn(info);
TargetReachWalk {
graph,
layers,
target_layer,
call_depth,
}
.run(&start)
}
struct TargetReachWalk<'a> {
graph: &'a CallGraph,
layers: &'a LayerDefinitions,
target_layer: &'a str,
call_depth: usize,
}
impl TargetReachWalk<'_> {
fn run(&self, start: &str) -> bool {
let Some(direct) = self.graph.forward.get(start) else {
return false;
};
let mut state = WalkState::seeded(start, direct);
while let Some((node, depth)) = state.queue.pop_front() {
if self.graph.layer_of(&node) == Some(self.target_layer) {
return true;
}
if depth < self.call_depth {
if let Some(callees) = self.graph.forward.get(&node) {
state.enqueue_unvisited(callees, depth + 1);
}
}
}
false
}
}