Skip to main content

code_split_core/
builder.rs

1use crate::graph::{Edge, EdgeKind, Graph, Node, NodeId, NodeKind};
2use std::collections::HashSet;
3
4#[derive(Debug, Default)]
5pub struct GraphBuilder {
6    graph: Graph,
7    seen_nodes: HashSet<NodeId>,
8}
9
10impl GraphBuilder {
11    pub fn new() -> Self {
12        Self::default()
13    }
14
15    pub fn add_node(&mut self, node: Node) -> bool {
16        if self.seen_nodes.insert(node.id.clone()) {
17            self.graph.nodes.push(node);
18            true
19        } else {
20            false
21        }
22    }
23
24    pub fn add_edge(&mut self, edge: Edge) {
25        self.graph.edges.push(edge);
26    }
27
28    pub fn node_count(&self) -> usize {
29        self.graph.nodes.len()
30    }
31
32    pub fn edge_count_of_kind(&self, kind: EdgeKind) -> usize {
33        self.graph.edges.iter().filter(|e| e.kind == kind).count()
34    }
35
36    pub fn nodes(&self) -> &Vec<Node> {
37        &self.graph.nodes
38    }
39
40    pub fn nodes_mut(&mut self) -> &mut Vec<Node> {
41        &mut self.graph.nodes
42    }
43
44    /// Look up an existing `Fn` or `Method` node by `(path, name)`.
45    /// Returns the node ID if exactly one match is found, `None` otherwise
46    /// (ambiguous or not found — caller must create a new node).
47    pub fn find_fn_node(&self, path: &str, name: &str) -> Option<NodeId> {
48        let mut found = None;
49        for node in &self.graph.nodes {
50            if matches!(node.kind, NodeKind::Fn | NodeKind::Method)
51                && node.path == path
52                && node.name == name
53            {
54                if found.is_some() {
55                    return None; // ambiguous — fall through to line-based lookup
56                }
57                found = Some(node.id.clone());
58            }
59        }
60        found
61    }
62
63    /// Line-based fallback: find an existing `Fn` or `Method` node by
64    /// `(path, name, line)`. Used when name-only lookup is ambiguous.
65    pub fn find_fn_node_by_line(&self, path: &str, name: &str, line: u32) -> Option<NodeId> {
66        self.graph
67            .nodes
68            .iter()
69            .find(|n| {
70                matches!(n.kind, NodeKind::Fn | NodeKind::Method)
71                    && n.path == path
72                    && n.name == name
73                    && n.line == Some(line)
74            })
75            .map(|n| n.id.clone())
76    }
77
78    pub fn build(self) -> Graph {
79        self.graph
80    }
81}
82
83#[cfg(test)]
84mod tests {
85    use super::*;
86    use crate::graph::NodeKind;
87
88    fn node(id: &str) -> Node {
89        Node {
90            id: id.into(),
91            kind: NodeKind::Crate,
92            name: id.into(),
93            path: String::new(),
94            parent: None,
95            external: None,
96            visibility: None,
97            loc: None,
98            line: None,
99            item_count: None,
100            method_count: None,
101            complexity: None,
102            cycle_kind: None,
103        }
104    }
105
106    #[test]
107    fn add_node_deduplicates_by_id() {
108        let mut b = GraphBuilder::new();
109        assert!(b.add_node(node("x")));
110        assert!(!b.add_node(node("x")));
111        let g = b.build();
112        assert_eq!(g.nodes.len(), 1);
113    }
114}