use codegraph::{CodeGraph, EdgeType, NodeId, NodeType, PropertyMap};
use codegraph_parser_api::{CodeIR, FileInfo, ParserError};
use std::collections::HashMap;
use std::path::Path;
use std::time::Duration;
pub fn ir_to_graph(
ir: &CodeIR,
graph: &mut CodeGraph,
file_path: &Path,
) -> Result<FileInfo, ParserError> {
let mut node_map: HashMap<String, NodeId> = HashMap::new();
let mut function_ids = Vec::new();
let mut class_ids = Vec::new();
let mut trait_ids = Vec::new();
let mut import_ids = Vec::new();
let file_id = if let Some(ref module) = ir.module {
let mut props = PropertyMap::new()
.with("name", module.name.clone())
.with("path", module.path.clone())
.with("language", module.language.clone())
.with("line_count", module.line_count.to_string());
if let Some(ref doc) = module.doc_comment {
props = props.with("doc", doc.clone());
}
let id = graph
.add_node(NodeType::CodeFile, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(module.name.clone(), id);
id
} else {
let name = file_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let props = PropertyMap::new()
.with("name", name.clone())
.with("path", file_path.display().to_string())
.with("language", "go");
let id = graph
.add_node(NodeType::CodeFile, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(name, id);
id
};
for func in &ir.functions {
let mut props = PropertyMap::new()
.with("name", func.name.clone())
.with("path", file_path.display().to_string())
.with("signature", func.signature.clone())
.with("visibility", func.visibility.clone())
.with("line_start", func.line_start as i64)
.with("line_end", func.line_end as i64)
.with("is_async", func.is_async.to_string())
.with("is_static", func.is_static.to_string());
if let Some(ref doc) = func.doc_comment {
props = props.with("doc", doc.clone());
}
if let Some(ref return_type) = func.return_type {
props = props.with("return_type", return_type.clone());
}
let func_id = graph
.add_node(NodeType::Function, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(func.name.clone(), func_id);
function_ids.push(func_id);
graph
.add_edge(file_id, func_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
for class in &ir.classes {
let mut props = PropertyMap::new()
.with("name", class.name.clone())
.with("path", file_path.display().to_string())
.with("visibility", class.visibility.clone())
.with("line_start", class.line_start as i64)
.with("line_end", class.line_end as i64)
.with("is_abstract", class.is_abstract.to_string());
if let Some(ref doc) = class.doc_comment {
props = props.with("doc", doc.clone());
}
let class_id = graph
.add_node(NodeType::Class, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(class.name.clone(), class_id);
class_ids.push(class_id);
graph
.add_edge(file_id, class_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
for method in &class.methods {
let method_name = format!("{}.{}", class.name, method.name);
let mut method_props = PropertyMap::new()
.with("name", method_name.clone())
.with("path", file_path.display().to_string())
.with("signature", method.signature.clone())
.with("visibility", method.visibility.clone())
.with("line_start", method.line_start as i64)
.with("line_end", method.line_end as i64)
.with("is_method", "true")
.with("parent_class", class.name.clone());
if let Some(ref doc) = method.doc_comment {
method_props = method_props.with("doc", doc.clone());
}
let method_id = graph
.add_node(NodeType::Function, method_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(method_name, method_id);
function_ids.push(method_id);
graph
.add_edge(class_id, method_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
}
for interface in &ir.traits {
let mut props = PropertyMap::new()
.with("name", interface.name.clone())
.with("path", file_path.display().to_string())
.with("visibility", interface.visibility.clone())
.with("line_start", interface.line_start as i64)
.with("line_end", interface.line_end as i64);
if let Some(ref doc) = interface.doc_comment {
props = props.with("doc", doc.clone());
}
let trait_id = graph
.add_node(NodeType::Interface, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(interface.name.clone(), trait_id);
trait_ids.push(trait_id);
graph
.add_edge(file_id, trait_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
for import in &ir.imports {
let imported_module = &import.imported;
let import_id = if let Some(&existing_id) = node_map.get(imported_module) {
existing_id
} else {
let props = PropertyMap::new()
.with("name", imported_module.clone())
.with("is_external", "true");
let id = graph
.add_node(NodeType::Module, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(imported_module.clone(), id);
id
};
import_ids.push(import_id);
let mut edge_props = PropertyMap::new();
if let Some(ref alias) = import.alias {
edge_props = edge_props.with("alias", alias.clone());
}
if import.is_wildcard {
edge_props = edge_props.with("is_wildcard", "true");
}
if !import.symbols.is_empty() {
edge_props = edge_props.with("symbols", import.symbols.join(","));
}
graph
.add_edge(file_id, import_id, EdgeType::Imports, edge_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
let mut unresolved_calls: HashMap<String, Vec<String>> = HashMap::new();
for call in &ir.calls {
if let Some(&caller_id) = node_map.get(&call.caller) {
if let Some(&callee_id) = node_map.get(&call.callee) {
let edge_props = PropertyMap::new()
.with("call_site_line", call.call_site_line.to_string())
.with("is_direct", call.is_direct.to_string());
graph
.add_edge(caller_id, callee_id, EdgeType::Calls, edge_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
} else {
unresolved_calls
.entry(call.caller.clone())
.or_default()
.push(call.callee.clone());
}
}
}
for (caller_name, callees) in unresolved_calls {
if let Some(&caller_id) = node_map.get(&caller_name) {
if let Ok(node) = graph.get_node(caller_id) {
let existing = node.properties.get_string("unresolved_calls").unwrap_or("");
let mut all_callees: Vec<&str> = if existing.is_empty() {
Vec::new()
} else {
existing.split(',').collect()
};
for callee in &callees {
if !all_callees.contains(&callee.as_str()) {
all_callees.push(callee);
}
}
let new_props = node
.properties
.clone()
.with("unresolved_calls", all_callees.join(","));
let _ = graph.update_node_properties(caller_id, new_props);
}
}
}
for inheritance in &ir.inheritance {
if let (Some(&child_id), Some(&parent_id)) = (
node_map.get(&inheritance.child),
node_map.get(&inheritance.parent),
) {
let edge_props = PropertyMap::new().with("order", inheritance.order.to_string());
graph
.add_edge(child_id, parent_id, EdgeType::Extends, edge_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
}
for impl_rel in &ir.implementations {
if let (Some(&implementor_id), Some(&trait_id)) = (
node_map.get(&impl_rel.implementor),
node_map.get(&impl_rel.trait_name),
) {
graph
.add_edge(
implementor_id,
trait_id,
EdgeType::Implements,
PropertyMap::new(),
)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
}
let line_count = if let Some(ref module) = ir.module {
module.line_count
} else {
0
};
Ok(FileInfo {
file_path: file_path.to_path_buf(),
file_id,
functions: function_ids,
classes: class_ids,
traits: trait_ids,
imports: import_ids,
parse_time: Duration::ZERO,
line_count,
byte_count: 0,
})
}
#[cfg(test)]
mod tests {
use super::*;
use codegraph_parser_api::{ClassEntity, FunctionEntity, ImportRelation, TraitEntity};
use std::path::PathBuf;
#[test]
fn test_ir_to_graph_empty() {
let ir = CodeIR::new(PathBuf::from("test.go"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 0);
assert_eq!(file_info.classes.len(), 0);
assert_eq!(file_info.traits.len(), 0);
}
#[test]
fn test_ir_to_graph_with_function() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.add_function(FunctionEntity::new("testFunc", 1, 5));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
}
#[test]
fn test_ir_to_graph_with_struct() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.add_class(ClassEntity::new("Person", 1, 10));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1);
}
#[test]
fn test_ir_to_graph_with_interface() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.add_trait(TraitEntity::new("Reader", 1, 5));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.traits.len(), 1);
}
#[test]
fn test_ir_to_graph_with_module() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.set_module(codegraph_parser_api::ModuleEntity::new(
"main", "test.go", "go",
));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
graph.get_node(file_info.file_id).unwrap();
}
#[test]
fn test_ir_to_graph_with_imports() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.add_import(ImportRelation::new("main", "fmt"));
ir.add_import(ImportRelation::new("main", "os"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.imports.len(), 2);
}
#[test]
fn test_ir_to_graph_with_methods() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
let mut class = ClassEntity::new("Calculator", 1, 10);
class.methods.push(FunctionEntity::new("Add", 2, 4));
class.methods.push(FunctionEntity::new("Subtract", 5, 7));
ir.add_class(class);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1);
assert_eq!(file_info.functions.len(), 2);
}
#[test]
fn test_ir_to_graph_function_properties() {
let mut ir = CodeIR::new(PathBuf::from("test.go"));
let func = FunctionEntity::new("publicFunc", 1, 5)
.with_visibility("public")
.with_signature("func publicFunc() error");
ir.add_function(func);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 1);
let func_node = graph.get_node(file_info.functions[0]).unwrap();
assert_eq!(
func_node.properties.get("name"),
Some(&codegraph::PropertyValue::String("publicFunc".to_string()))
);
assert_eq!(
func_node.properties.get("visibility"),
Some(&codegraph::PropertyValue::String("public".to_string()))
);
}
#[test]
fn test_ir_to_graph_with_implementation() {
use codegraph::EdgeType;
use codegraph_parser_api::ImplementationRelation;
let mut ir = CodeIR::new(PathBuf::from("test.go"));
ir.add_class(ClassEntity::new("FileReader", 1, 20));
ir.add_trait(TraitEntity::new("Reader", 22, 30));
ir.add_implementation(ImplementationRelation::new("FileReader", "Reader"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.go").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 1);
assert_eq!(file_info.traits.len(), 1);
let struct_id = file_info.classes[0];
let interface_id = file_info.traits[0];
let edges = graph.get_edges_between(struct_id, interface_id).unwrap();
assert!(
!edges.is_empty(),
"Should have implements edge between struct and interface"
);
let edge = graph.get_edge(edges[0]).unwrap();
assert_eq!(
edge.edge_type,
EdgeType::Implements,
"Edge should be of type Implements"
);
}
}