use codegraph::{helpers, CodeGraph, EdgeType, NodeId, NodeType, PropertyMap};
use codegraph_parser_api::CodeIR;
use std::collections::HashMap;
use crate::error::Result;
pub fn build_graph(graph: &mut CodeGraph, ir: &CodeIR, file_path: &str) -> Result<NodeId> {
let file_id = helpers::add_file(graph, file_path, "python")
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
let mut entity_map: HashMap<String, NodeId> = HashMap::new();
for func in &ir.functions {
let mut props = PropertyMap::new()
.with("name", func.name.clone())
.with("signature", func.signature.clone())
.with("line_start", func.line_start as i64)
.with("line_end", func.line_end as i64)
.with("visibility", func.visibility.clone())
.with("is_async", func.is_async)
.with("is_test", func.is_test);
if let Some(ref complexity) = func.complexity {
props = props
.with("complexity", complexity.cyclomatic_complexity as i64)
.with("complexity_grade", complexity.grade().to_string())
.with("complexity_branches", complexity.branches as i64)
.with("complexity_loops", complexity.loops as i64)
.with(
"complexity_logical_ops",
complexity.logical_operators as i64,
)
.with("complexity_nesting", complexity.max_nesting_depth as i64)
.with(
"complexity_exceptions",
complexity.exception_handlers as i64,
)
.with("complexity_early_returns", complexity.early_returns as i64);
}
let func_id = graph
.add_node(NodeType::Function, props)
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
graph
.add_edge(file_id, func_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
entity_map.insert(func.name.clone(), func_id);
}
for class in &ir.classes {
let class_id = helpers::add_class(
graph,
file_id,
&class.name,
class.line_start as i64,
class.line_end as i64,
)
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
entity_map.insert(class.name.clone(), class_id);
for method in &class.methods {
let method_id = helpers::add_method(
graph,
class_id,
&method.name,
method.line_start as i64,
method.line_end as i64,
)
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
let qualified = format!("{}.{}", class.name, method.name);
entity_map.insert(qualified, method_id);
}
}
for call in &ir.calls {
if let (Some(&caller_id), Some(&callee_id)) =
(entity_map.get(&call.caller), entity_map.get(&call.callee))
{
helpers::add_call(graph, caller_id, callee_id, call.call_site_line as i64)
.map_err(|e| crate::error::ParseError::GraphError(e.to_string()))?;
}
}
let _ = &ir.imports;
Ok(file_id)
}
#[cfg(test)]
mod tests {
use super::*;
use codegraph_parser_api::{CallRelation, ClassEntity, FunctionEntity, ImportRelation};
#[test]
fn test_build_empty_module() {
let mut graph = CodeGraph::in_memory().unwrap();
let ir = CodeIR::new(std::path::PathBuf::from("test.py"));
let result = build_graph(&mut graph, &ir, "test.py");
assert!(result.is_ok());
}
#[test]
fn test_build_with_function() {
let mut graph = CodeGraph::in_memory().unwrap();
let mut ir = CodeIR::new(std::path::PathBuf::from("test.py"));
ir.add_function(FunctionEntity::new("test_func", 1, 3));
let result = build_graph(&mut graph, &ir, "test.py");
assert!(result.is_ok());
}
#[test]
fn test_build_with_class() {
let mut graph = CodeGraph::in_memory().unwrap();
let mut ir = CodeIR::new(std::path::PathBuf::from("test.py"));
ir.add_class(ClassEntity::new("MyClass", 1, 4));
let result = build_graph(&mut graph, &ir, "test.py");
assert!(result.is_ok());
}
#[test]
fn test_build_with_relationships() {
let mut graph = CodeGraph::in_memory().unwrap();
let mut ir = CodeIR::new(std::path::PathBuf::from("test.py"));
ir.add_function(FunctionEntity::new("caller", 1, 3));
ir.add_function(FunctionEntity::new("callee", 5, 7));
ir.add_call(CallRelation::new("caller", "callee", 2));
ir.add_import(ImportRelation::new("test", "os"));
let result = build_graph(&mut graph, &ir, "test.py");
assert!(result.is_ok());
}
}