use crate::file_analysis::{CrossFileLookup, FileAnalysis};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum EdgeKind {
Inherits,
InheritsInv,
Bridges,
}
impl EdgeKind {
pub const ALL: [EdgeKind; 3] = [Self::Inherits, Self::InheritsInv, Self::Bridges];
fn flag(self) -> EdgeKindMask {
match self {
EdgeKind::Inherits => EdgeKindMask::INHERITS,
EdgeKind::InheritsInv => EdgeKindMask::INHERITS_INV,
EdgeKind::Bridges => EdgeKindMask::BRIDGES,
}
}
}
bitflags::bitflags! {
#[derive(Clone, Copy, PartialEq, Eq)]
pub struct EdgeKindMask: u8 {
const INHERITS = 1 << 0;
const INHERITS_INV = 1 << 1;
const BRIDGES = 1 << 2;
}
}
#[derive(Debug, Clone, PartialEq, Eq, Hash)]
pub enum Node {
Class(String),
Module(String),
}
pub struct GraphView<'a> {
fa: &'a FileAnalysis,
idx: Option<&'a dyn CrossFileLookup>,
}
impl<'a> GraphView<'a> {
pub fn new(fa: &'a FileAnalysis, idx: Option<&'a dyn CrossFileLookup>) -> Self {
GraphView { fa, idx }
}
pub fn walk(
&self,
origin: Node,
mask: EdgeKindMask,
visit: &mut dyn FnMut(&Node) -> std::ops::ControlFlow<()>,
) {
let mut seen: std::collections::HashSet<Node> = std::collections::HashSet::new();
seen.insert(origin.clone());
let mut stack: Vec<(Node, usize)> = vec![(origin, 0)];
const MAX_DEPTH: usize = 21; while let Some((node, depth)) = stack.pop() {
if depth > 0 && visit(&node).is_break() {
return;
}
if depth >= MAX_DEPTH {
continue;
}
let mut next: Vec<Node> = Vec::new();
self.edges_from(&node, mask, &mut next);
for n in next.into_iter().rev() {
if seen.insert(n.clone()) {
stack.push((n, depth + 1));
}
}
}
}
fn edges_from(&self, node: &Node, mask: EdgeKindMask, out: &mut Vec<Node>) {
let Node::Class(class) = node else { return };
for kind in EdgeKind::ALL {
if !mask.contains(kind.flag()) {
continue;
}
match kind {
EdgeKind::Inherits => {
for p in crate::file_analysis::parents_of(
class,
&self.fa.package_parents,
self.idx,
&self.fa.app_surface_consumers,
) {
out.push(Node::Class(p));
}
}
EdgeKind::InheritsInv => {
if let Some(idx) = self.idx {
for (pkg, _module) in idx.direct_children_of(class) {
out.push(Node::Class(pkg));
}
}
}
EdgeKind::Bridges => {
if let Some(idx) = self.idx {
let mut seen_mods: std::collections::HashSet<String> = Default::default();
idx.for_each_entity_bridged_to(class, &mut |module, _cached, _sym| {
if seen_mods.insert(module.to_string()) {
out.push(Node::Module(module.to_string()));
}
});
}
}
}
}
}
}
#[cfg(test)]
#[path = "graph_tests.rs"]
mod tests;