use codegraph::{CodeGraph, EdgeType, NodeId, NodeType, PropertyMap};
use codegraph_parser_api::{CodeIR, FileInfo, ParserError};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
use std::time::Duration;
fn resolve_import_path(importing_file: &Path, import_path: &str) -> Option<PathBuf> {
if !import_path.starts_with('.') && !import_path.starts_with('/') {
return None;
}
let parent_dir = importing_file.parent()?;
let resolved = if let Some(stripped) = import_path.strip_prefix("./") {
parent_dir.join(stripped)
} else if import_path.starts_with("../") {
let mut current = parent_dir.to_path_buf();
let mut remaining = import_path;
while let Some(rest) = remaining.strip_prefix("../") {
current = current.parent()?.to_path_buf();
remaining = rest;
}
current.join(remaining)
} else if let Some(stripped) = import_path.strip_prefix('/') {
PathBuf::from(format!("/{stripped}"))
} else {
return None;
};
Some(resolved)
}
fn normalize_path_for_matching(path: &Path) -> String {
let path_str = path.to_string_lossy();
let without_ext = if path_str.ends_with(".d.ts") {
path_str.trim_end_matches(".d.ts")
} else {
path_str
.trim_end_matches(".ts")
.trim_end_matches(".tsx")
.trim_end_matches(".js")
.trim_end_matches(".jsx")
};
without_ext.to_string()
}
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 as i64);
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 file_name = file_path
.file_stem()
.and_then(|s| s.to_str())
.unwrap_or("unknown")
.to_string();
let props = PropertyMap::new()
.with("name", file_name.clone())
.with("path", file_path.display().to_string())
.with("language", "typescript");
let id = graph
.add_node(NodeType::CodeFile, props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
node_map.insert(file_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("line_start", func.line_start as i64)
.with("line_end", func.line_end as i64)
.with("is_async", func.is_async);
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| ParserError::GraphError(e.to_string()))?;
node_map.insert(func.name.clone(), func_id);
function_ids.push(func_id);
if let Some(ref parent_class) = func.parent_class {
if let Some(&class_id) = node_map.get(parent_class) {
graph
.add_edge(class_id, func_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
} else {
graph
.add_edge(file_id, func_id, EdgeType::Contains, PropertyMap::new())
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
} else {
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 props = PropertyMap::new()
.with("name", class.name.clone())
.with("path", file_path.display().to_string())
.with("line_start", class.line_start as i64)
.with("line_end", class.line_end as i64);
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 interface in &ir.traits {
let props = PropertyMap::new()
.with("name", interface.name.clone())
.with("path", file_path.display().to_string())
.with("line_start", interface.line_start as i64)
.with("line_end", interface.line_end as i64);
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 resolved_path = resolve_import_path(file_path, imported_module);
let normalized_resolved = resolved_path
.as_ref()
.map(|p| normalize_path_for_matching(p));
let mut target_node_id: Option<NodeId> = None;
if let Some(ref resolved) = normalized_resolved {
let import_file_stem = PathBuf::from(resolved)
.file_stem()
.and_then(|s| s.to_str())
.map(|s| s.to_string());
if let Some(ref stem) = import_file_stem {
target_node_id = node_map.get(stem).copied();
}
}
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(","));
}
if let Some(ref resolved) = normalized_resolved {
edge_props = edge_props.with("resolved_path", resolved.clone());
}
let mut symbols_linked = false;
if !import.symbols.is_empty() {
for symbol in &import.symbols {
if let Some(&symbol_id) = node_map.get(symbol) {
let symbol_edge_props = edge_props
.clone()
.with("imported_symbol", symbol.clone())
.with("source_module", imported_module.clone());
graph
.add_edge(file_id, symbol_id, EdgeType::Imports, symbol_edge_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
symbols_linked = true;
}
}
}
let import_id = if let Some(id) = target_node_id {
id
} else if let Some(&existing_id) = node_map.get(imported_module) {
existing_id
} else {
let is_external = resolved_path.is_none();
let props = PropertyMap::new()
.with("name", imported_module.clone())
.with("is_external", is_external.to_string());
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);
if !symbols_linked || import.symbols.is_empty() {
graph
.add_edge(file_id, import_id, EdgeType::Imports, edge_props)
.map_err(|e| ParserError::GraphError(e.to_string()))?;
}
}
let mut unresolved_calls: std::collections::HashMap<String, Vec<String>> =
std::collections::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()))?;
}
}
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: 0,
byte_count: 0,
})
}
#[cfg(test)]
mod tests {
use super::*;
use codegraph_parser_api::{ClassEntity, FunctionEntity, TraitEntity};
use std::path::PathBuf;
#[test]
fn test_ir_to_graph_empty() {
let ir = CodeIR::new(PathBuf::from("test.ts"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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.ts"));
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.ts").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_class() {
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
ir.add_class(ClassEntity::new("TestClass", 1, 10));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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.ts"));
ir.add_trait(TraitEntity::new("ITest", 1, 5));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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.ts"));
ir.set_module(codegraph_parser_api::ModuleEntity::new(
"test",
"test.ts",
"typescript",
));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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_async_function() {
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
let func = FunctionEntity::new("asyncFunc", 1, 5).async_fn();
ir.add_function(func);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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("is_async"),
Some(&codegraph::PropertyValue::Bool(true))
);
}
#[test]
fn test_ir_to_graph_with_imports() {
use codegraph::{Direction, EdgeType};
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
ir.add_import(ImportRelation::new("test.ts", "react"));
ir.add_import(
ImportRelation::new("test.ts", "lodash")
.with_symbols(vec!["map".to_string(), "filter".to_string()]),
);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.imports.len(), 2, "Should have 2 import nodes");
let neighbors = graph
.get_neighbors(file_info.file_id, Direction::Outgoing)
.unwrap();
let mut import_edges_count = 0;
for neighbor_id in &neighbors {
let edges = graph
.get_edges_between(file_info.file_id, *neighbor_id)
.unwrap();
for edge_id in edges {
let edge = graph.get_edge(edge_id).unwrap();
if edge.edge_type == EdgeType::Imports {
import_edges_count += 1;
}
}
}
assert_eq!(import_edges_count, 2, "Should have 2 import edges");
}
#[test]
fn test_ir_to_graph_with_calls() {
use codegraph::EdgeType;
use codegraph_parser_api::CallRelation;
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
ir.add_function(FunctionEntity::new("caller", 1, 10));
ir.add_function(FunctionEntity::new("callee", 12, 20));
ir.add_call(CallRelation::new("caller", "callee", 5));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.functions.len(), 2);
let caller_id = file_info.functions[0];
let callee_id = file_info.functions[1];
let edges = graph.get_edges_between(caller_id, callee_id).unwrap();
assert!(
!edges.is_empty(),
"Should have call edge between caller and callee"
);
let edge = graph.get_edge(edges[0]).unwrap();
assert_eq!(
edge.edge_type,
EdgeType::Calls,
"Edge should be of type Calls"
);
}
#[test]
fn test_ir_to_graph_with_inheritance() {
use codegraph::EdgeType;
use codegraph_parser_api::InheritanceRelation;
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
ir.add_class(ClassEntity::new("ChildClass", 1, 20));
ir.add_class(ClassEntity::new("ParentClass", 22, 40));
ir.add_inheritance(InheritanceRelation::new("ChildClass", "ParentClass"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").as_path());
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.classes.len(), 2);
let child_id = file_info.classes[0];
let parent_id = file_info.classes[1];
let edges = graph.get_edges_between(child_id, parent_id).unwrap();
assert!(
!edges.is_empty(),
"Should have extends edge between child and parent"
);
let edge = graph.get_edge(edges[0]).unwrap();
assert_eq!(
edge.edge_type,
EdgeType::Extends,
"Edge should be of type Extends"
);
}
#[test]
fn test_ir_to_graph_with_implementation() {
use codegraph::EdgeType;
use codegraph_parser_api::ImplementationRelation;
let mut ir = CodeIR::new(PathBuf::from("test.ts"));
ir.add_class(ClassEntity::new("MyClass", 1, 20));
ir.add_trait(TraitEntity::new("IMyInterface", 22, 30));
ir.add_implementation(ImplementationRelation::new("MyClass", "IMyInterface"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(&ir, &mut graph, PathBuf::from("test.ts").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 class_id = file_info.classes[0];
let interface_id = file_info.traits[0];
let edges = graph.get_edges_between(class_id, interface_id).unwrap();
assert!(
!edges.is_empty(),
"Should have implements edge between class and interface"
);
let edge = graph.get_edge(edges[0]).unwrap();
assert_eq!(
edge.edge_type,
EdgeType::Implements,
"Edge should be of type Implements"
);
}
#[test]
fn test_resolve_import_path_relative_same_dir() {
let file_path = PathBuf::from("/src/extension.ts");
let result = resolve_import_path(&file_path, "./toolManager");
assert!(result.is_some());
assert_eq!(result.unwrap(), PathBuf::from("/src/toolManager"));
}
#[test]
fn test_resolve_import_path_relative_subdir() {
let file_path = PathBuf::from("/src/extension.ts");
let result = resolve_import_path(&file_path, "./ai/toolManager");
assert!(result.is_some());
assert_eq!(result.unwrap(), PathBuf::from("/src/ai/toolManager"));
}
#[test]
fn test_resolve_import_path_parent_dir() {
let file_path = PathBuf::from("/src/ai/toolManager.ts");
let result = resolve_import_path(&file_path, "../extension");
assert!(result.is_some());
assert_eq!(result.unwrap(), PathBuf::from("/src/extension"));
}
#[test]
fn test_resolve_import_path_multiple_parent_dirs() {
let file_path = PathBuf::from("/src/ai/tools/manager.ts");
let result = resolve_import_path(&file_path, "../../extension");
assert!(result.is_some());
assert_eq!(result.unwrap(), PathBuf::from("/src/extension"));
}
#[test]
fn test_resolve_import_path_external_package() {
let file_path = PathBuf::from("/src/extension.ts");
let result = resolve_import_path(&file_path, "vscode");
assert!(result.is_none(), "External packages should return None");
}
#[test]
fn test_resolve_import_path_scoped_package() {
let file_path = PathBuf::from("/src/extension.ts");
let result = resolve_import_path(&file_path, "@types/node");
assert!(result.is_none(), "Scoped packages should return None");
}
#[test]
fn test_normalize_path_removes_ts_extension() {
let path = PathBuf::from("/src/toolManager.ts");
assert_eq!(normalize_path_for_matching(&path), "/src/toolManager");
}
#[test]
fn test_normalize_path_removes_tsx_extension() {
let path = PathBuf::from("/src/Component.tsx");
assert_eq!(normalize_path_for_matching(&path), "/src/Component");
}
#[test]
fn test_normalize_path_removes_dts_extension() {
let path = PathBuf::from("/types/index.d.ts");
assert_eq!(normalize_path_for_matching(&path), "/types/index");
}
#[test]
fn test_normalize_path_no_extension() {
let path = PathBuf::from("/src/toolManager");
assert_eq!(normalize_path_for_matching(&path), "/src/toolManager");
}
#[test]
fn test_import_creates_edge_to_symbol() {
use codegraph::EdgeType;
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("/src/extension.ts"));
ir.add_class(ClassEntity::new("MyClass", 10, 20));
ir.add_import(
ImportRelation::new("/src/extension.ts", "./module")
.with_symbols(vec!["MyClass".to_string()]),
);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(
&ir,
&mut graph,
PathBuf::from("/src/extension.ts").as_path(),
);
assert!(result.is_ok());
let file_info = result.unwrap();
let class_id = file_info.classes[0];
let edges = graph
.get_edges_between(file_info.file_id, class_id)
.unwrap();
let import_edges: Vec<_> = edges
.iter()
.filter_map(|e| graph.get_edge(*e).ok())
.filter(|e| e.edge_type == EdgeType::Imports)
.collect();
assert!(
!import_edges.is_empty(),
"Should have import edge from file to the imported class"
);
let edge = &import_edges[0];
assert_eq!(
edge.properties.get_string("imported_symbol"),
Some("MyClass")
);
}
#[test]
fn test_import_creates_edge_to_function() {
use codegraph::EdgeType;
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("/src/extension.ts"));
ir.add_function(FunctionEntity::new("myFunction", 5, 15));
ir.add_import(
ImportRelation::new("/src/extension.ts", "./utils")
.with_symbols(vec!["myFunction".to_string()]),
);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(
&ir,
&mut graph,
PathBuf::from("/src/extension.ts").as_path(),
);
assert!(result.is_ok());
let file_info = result.unwrap();
let func_id = file_info.functions[0];
let edges = graph.get_edges_between(file_info.file_id, func_id).unwrap();
let import_edges: Vec<_> = edges
.iter()
.filter_map(|e| graph.get_edge(*e).ok())
.filter(|e| e.edge_type == EdgeType::Imports)
.collect();
assert!(
!import_edges.is_empty(),
"Should have import edge from file to the imported function"
);
}
#[test]
fn test_import_multiple_symbols_creates_multiple_edges() {
use codegraph::EdgeType;
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("/src/extension.ts"));
ir.add_class(ClassEntity::new("ClassA", 10, 20));
ir.add_class(ClassEntity::new("ClassB", 25, 35));
ir.add_import(
ImportRelation::new("/src/extension.ts", "./module")
.with_symbols(vec!["ClassA".to_string(), "ClassB".to_string()]),
);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(
&ir,
&mut graph,
PathBuf::from("/src/extension.ts").as_path(),
);
assert!(result.is_ok());
let file_info = result.unwrap();
for class_id in &file_info.classes {
let edges = graph
.get_edges_between(file_info.file_id, *class_id)
.unwrap();
let import_edges: Vec<_> = edges
.iter()
.filter_map(|e| graph.get_edge(*e).ok())
.filter(|e| e.edge_type == EdgeType::Imports)
.collect();
assert!(
!import_edges.is_empty(),
"Each imported class should have an import edge"
);
}
}
#[test]
fn test_external_import_creates_module_node() {
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("/src/extension.ts"));
ir.add_import(
ImportRelation::new("/src/extension.ts", "react")
.with_symbols(vec!["useState".to_string()]),
);
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(
&ir,
&mut graph,
PathBuf::from("/src/extension.ts").as_path(),
);
assert!(result.is_ok());
let file_info = result.unwrap();
assert_eq!(file_info.imports.len(), 1);
let import_node = graph.get_node(file_info.imports[0]).unwrap();
assert_eq!(import_node.node_type, NodeType::Module);
assert_eq!(import_node.properties.get_string("name"), Some("react"));
assert_eq!(
import_node.properties.get_string("is_external"),
Some("true")
);
}
#[test]
fn test_import_stores_resolved_path() {
use codegraph::{Direction, EdgeType};
use codegraph_parser_api::ImportRelation;
let mut ir = CodeIR::new(PathBuf::from("/src/extension.ts"));
ir.add_import(ImportRelation::new("/src/extension.ts", "./ai/tools"));
let mut graph = CodeGraph::in_memory().unwrap();
let result = ir_to_graph(
&ir,
&mut graph,
PathBuf::from("/src/extension.ts").as_path(),
);
assert!(result.is_ok());
let file_info = result.unwrap();
let neighbors = graph
.get_neighbors(file_info.file_id, Direction::Outgoing)
.unwrap();
let mut found_resolved_path = false;
for neighbor_id in &neighbors {
let edges = graph
.get_edges_between(file_info.file_id, *neighbor_id)
.unwrap();
for edge_id in edges {
let edge = graph.get_edge(edge_id).unwrap();
if edge.edge_type == EdgeType::Imports {
if let Some(resolved) = edge.properties.get_string("resolved_path") {
assert!(resolved.contains("ai/tools"));
found_resolved_path = true;
}
}
}
}
assert!(
found_resolved_path,
"Import edge should have resolved_path property for relative imports"
);
}
}