1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
// DagBuilder core: constructor, build_from_project, finalize_graph, build_from_project_with_limit
impl DagBuilder {
#[must_use]
pub fn new() -> Self {
Self {
graph: DependencyGraph::new(),
function_map: FxHashMap::default(),
type_map: FxHashMap::default(),
namer: SemanticNamer::new(),
}
}
#[must_use]
pub fn build_from_project(project: &ProjectContext) -> DependencyGraph {
let mut builder = Self::new();
// First pass: collect all nodes and build lookup maps
for file in &project.files {
builder.collect_nodes(file);
}
// Second pass: create edges based on relationships
for file in &project.files {
builder.process_relationships(file);
}
builder.finalize_graph()
}
const EDGE_BUDGET: usize = 400; // Empirically derived Mermaid limit
fn finalize_graph(mut self) -> DependencyGraph {
// First, remove edges that reference non-existent nodes
let valid_nodes: FxHashSet<&String> = self.graph.nodes.keys().collect();
self.graph
.edges
.retain(|edge| valid_nodes.contains(&edge.from) && valid_nodes.contains(&edge.to));
if self.graph.edges.len() > Self::EDGE_BUDGET {
self.prune_edges_by_priority();
}
self.graph
}
/// Prune edges when over budget, keeping highest-priority edge types
fn prune_edges_by_priority(&mut self) {
// Priority-based edge sorting (Inherits > Uses > Implements > Call > Import)
let priority = |edge_type: &EdgeType| -> u8 {
match edge_type {
EdgeType::Inherits => 0,
EdgeType::Uses => 1,
EdgeType::Implements => 2,
EdgeType::Calls => 3,
EdgeType::Imports => 4,
}
};
// Sort edges by priority (lower number = higher priority)
self.graph
.edges
.sort_unstable_by_key(|e| priority(&e.edge_type));
// Truncate to budget limit
self.graph.edges.truncate(Self::EDGE_BUDGET);
// Maintain node consistency - only keep nodes referenced in remaining edges
let retained_nodes: FxHashSet<String> = self
.graph
.edges
.iter()
.flat_map(|e| [e.from.clone(), e.to.clone()])
.collect();
self.graph.nodes.retain(|id, _| retained_nodes.contains(id));
}
#[must_use]
pub fn build_from_project_with_limit(
project: &ProjectContext,
max_nodes: usize,
) -> DependencyGraph {
let graph = Self::build_from_project(project);
// Always calculate PageRank scores for centrality (takes ownership, no clone)
let graph = add_pagerank_scores(graph);
if graph.edges.len() > 400 {
// Safety margin for Mermaid - prune but keep scores
prune_graph_pagerank(&graph, max_nodes)
} else {
graph
}
}
}
impl Default for DagBuilder {
fn default() -> Self {
Self::new()
}
}