ryo-analysis 0.1.0

Code graph and discovery engine for the RYO project
Documentation
//! GraphBuilderV2 - Build CodeGraphV2 from parsed code.
//!
//! Key differences from V1:
//! - Uses CodeGraphV2 (petgraph-free)
//! - Uses MatchExprDataV2 (String-free: FileId + SymbolId)
//! - Incrementally builds kind index during add_symbol

use super::graph_v2::{CodeEdgeV2, CodeGraphV2, MatchExprDataV2};
use crate::symbol::{FileId, SymbolId, SymbolPath, SymbolRegistry};
use crate::SymbolKind;

/// Builder for constructing CodeGraphV2.
///
/// Provides a convenient API for adding symbols and relationships to the graph.
///
/// # Example
///
/// ```rust,ignore
/// let mut registry = SymbolRegistry::new();
/// let mut builder = GraphBuilderV2::new(&mut registry);
///
/// let foo = builder.add_symbol(SymbolPath::parse("mylib::foo")?, SymbolKind::Function)?;
/// let bar = builder.add_symbol(SymbolPath::parse("mylib::bar")?, SymbolKind::Function)?;
/// builder.add_call(foo, bar);
///
/// let graph = builder.build();
/// ```
pub struct GraphBuilderV2<'a> {
    graph: CodeGraphV2,
    registry: &'a mut SymbolRegistry,
}

impl<'a> GraphBuilderV2<'a> {
    /// Create a new builder.
    pub fn new(registry: &'a mut SymbolRegistry) -> Self {
        Self {
            graph: CodeGraphV2::new(),
            registry,
        }
    }

    /// Create a new builder with pre-allocated capacity.
    pub fn with_capacity(registry: &'a mut SymbolRegistry, nodes: usize, edges: usize) -> Self {
        Self {
            graph: CodeGraphV2::with_capacity(nodes, edges),
            registry,
        }
    }

    // ========================================================================
    // Symbol Management
    // ========================================================================

    /// Add a symbol to both registry and graph.
    ///
    /// Returns the SymbolId for the added symbol.
    /// Also adds the symbol to the kind index for efficient kind-based queries.
    pub fn add_symbol(
        &mut self,
        path: SymbolPath,
        kind: SymbolKind,
    ) -> Result<SymbolId, crate::symbol::RegistrationError> {
        let id = self.registry.register(path, kind)?;
        self.graph.add_node(id);
        self.graph.add_to_kind_index(id, kind);
        Ok(id)
    }

    /// Add an existing symbol to the graph (already registered in registry).
    ///
    /// Use this when the symbol is already registered but not yet in the graph.
    pub fn add_existing_symbol(&mut self, id: SymbolId, kind: SymbolKind) {
        self.graph.add_node(id);
        self.graph.add_to_kind_index(id, kind);
    }

    /// Add a crate root.
    pub fn add_crate_root(
        &mut self,
        path: SymbolPath,
    ) -> Result<SymbolId, crate::symbol::RegistrationError> {
        let id = self.registry.register(path, SymbolKind::Mod)?;
        self.graph.add_crate_root(id);
        self.graph.add_to_kind_index(id, SymbolKind::Mod);
        Ok(id)
    }

    /// Add an existing symbol as a crate root.
    pub fn add_existing_crate_root(&mut self, id: SymbolId) {
        self.graph.add_crate_root(id);
        self.graph.add_to_kind_index(id, SymbolKind::Mod);
    }

    // ========================================================================
    // Edge Management
    // ========================================================================

    /// Add a Contains relationship (parent → child).
    pub fn add_contains(&mut self, parent: SymbolId, child: SymbolId) {
        self.graph.add_edge(parent, child, CodeEdgeV2::Contains);
    }

    /// Add a Calls relationship (caller → callee).
    pub fn add_call(&mut self, caller: SymbolId, callee: SymbolId) {
        self.graph.add_edge(caller, callee, CodeEdgeV2::Calls);
    }

    /// Add an Implements relationship (implementor → trait/type).
    pub fn add_implements(&mut self, implementor: SymbolId, trait_or_type: SymbolId) {
        self.graph
            .add_edge(implementor, trait_or_type, CodeEdgeV2::Implements);
    }

    // ========================================================================
    // Match Expression Management
    // ========================================================================

    /// Add a match expression to a function (String-free version).
    ///
    /// Uses FileId and SymbolId instead of PathBuf and String.
    ///
    /// # Arguments
    ///
    /// * `function_id` - The function containing the match expression
    /// * `file_id` - The file containing the match expression
    /// * `enum_id` - The enum being matched
    /// * `offset` - Byte offset in the file
    /// * `line` - Line number (1-indexed)
    pub fn add_match_expr(
        &mut self,
        function_id: SymbolId,
        file_id: FileId,
        enum_id: SymbolId,
        offset: u32,
        line: u32,
    ) {
        self.graph.add_match_expr(
            function_id,
            MatchExprDataV2 {
                file_id,
                enum_id,
                offset,
                line,
            },
        );
    }

    // ========================================================================
    // Build
    // ========================================================================

    /// Build the graph, consuming the builder.
    ///
    /// Note: Unlike V1, indices are built incrementally during add_symbol,
    /// so no post-build index rebuild is needed.
    pub fn build(self) -> CodeGraphV2 {
        self.graph
    }

    /// Get a reference to the registry.
    pub fn registry(&self) -> &SymbolRegistry {
        self.registry
    }

    /// Get a mutable reference to the registry.
    pub fn registry_mut(&mut self) -> &mut SymbolRegistry {
        self.registry
    }

    /// Get a reference to the graph being built.
    pub fn graph(&self) -> &CodeGraphV2 {
        &self.graph
    }
}

// ============================================================================
// Tests
// ============================================================================

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_builder_basic() {
        let mut registry = SymbolRegistry::new();
        let mut builder = GraphBuilderV2::new(&mut registry);

        let foo = builder
            .add_symbol(
                SymbolPath::parse("mylib::foo").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();
        let bar = builder
            .add_symbol(
                SymbolPath::parse("mylib::bar").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();

        builder.add_call(foo, bar);

        let graph = builder.build();
        assert_eq!(graph.node_count(), 2);
        assert_eq!(graph.edge_count(), 1);
    }

    #[test]
    fn test_builder_module_structure() {
        let mut registry = SymbolRegistry::new();
        let mut builder = GraphBuilderV2::new(&mut registry);

        let crate_root = builder
            .add_crate_root(SymbolPath::parse("mylib").unwrap())
            .unwrap();
        let module = builder
            .add_symbol(
                SymbolPath::parse("mylib::handlers").unwrap(),
                SymbolKind::Mod,
            )
            .unwrap();
        let func = builder
            .add_symbol(
                SymbolPath::parse("mylib::handlers::handle").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();

        builder.add_contains(crate_root, module);
        builder.add_contains(module, func);

        let graph = builder.build();
        let children: Vec<_> = graph.children_of(crate_root).collect();
        assert_eq!(children.len(), 1);

        let module_children: Vec<_> = graph.children_of(module).collect();
        assert_eq!(module_children.len(), 1);

        assert_eq!(graph.parent_of(func), Some(module));
    }

    #[test]
    fn test_builder_kind_index() {
        let mut registry = SymbolRegistry::new();
        let mut builder = GraphBuilderV2::new(&mut registry);

        builder
            .add_symbol(SymbolPath::parse("mylib::Foo").unwrap(), SymbolKind::Struct)
            .unwrap();
        builder
            .add_symbol(
                SymbolPath::parse("mylib::bar").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();
        builder
            .add_symbol(
                SymbolPath::parse("mylib::baz").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();

        let graph = builder.build();

        let structs: Vec<_> = graph.iter_by_kind(SymbolKind::Struct).collect();
        assert_eq!(structs.len(), 1);

        let functions: Vec<_> = graph.iter_by_kind(SymbolKind::Function).collect();
        assert_eq!(functions.len(), 2);
    }

    #[test]
    fn test_builder_with_capacity() {
        let mut registry = SymbolRegistry::new();
        let builder = GraphBuilderV2::with_capacity(&mut registry, 100, 200);
        let graph = builder.build();
        assert!(graph.is_empty());
    }

    #[test]
    fn test_builder_callers_callees() {
        let mut registry = SymbolRegistry::new();
        let mut builder = GraphBuilderV2::new(&mut registry);

        let main = builder
            .add_symbol(
                SymbolPath::parse("mylib::main").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();
        let helper1 = builder
            .add_symbol(
                SymbolPath::parse("mylib::helper1").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();
        let helper2 = builder
            .add_symbol(
                SymbolPath::parse("mylib::helper2").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();
        let util = builder
            .add_symbol(
                SymbolPath::parse("mylib::util").unwrap(),
                SymbolKind::Function,
            )
            .unwrap();

        // main calls helper1 and helper2
        builder.add_call(main, helper1);
        builder.add_call(main, helper2);
        // helper1 and helper2 both call util
        builder.add_call(helper1, util);
        builder.add_call(helper2, util);

        let graph = builder.build();

        // Check callees of main
        let main_callees: Vec<_> = graph.callees_of(main).collect();
        assert_eq!(main_callees.len(), 2);

        // Check callers of util
        let util_callers: Vec<_> = graph.callers_of(util).collect();
        assert_eq!(util_callers.len(), 2);
    }

    #[test]
    fn test_builder_implements() {
        let mut registry = SymbolRegistry::new();
        let mut builder = GraphBuilderV2::new(&mut registry);

        let trait_id = builder
            .add_symbol(
                SymbolPath::parse("mylib::MyTrait").unwrap(),
                SymbolKind::Trait,
            )
            .unwrap();
        let impl1 = builder
            .add_symbol(SymbolPath::parse("mylib::Foo").unwrap(), SymbolKind::Struct)
            .unwrap();
        let impl2 = builder
            .add_symbol(SymbolPath::parse("mylib::Bar").unwrap(), SymbolKind::Struct)
            .unwrap();

        builder.add_implements(impl1, trait_id);
        builder.add_implements(impl2, trait_id);

        let graph = builder.build();

        let implementors: Vec<_> = graph.implementors_of(trait_id).collect();
        assert_eq!(implementors.len(), 2);
    }
}