use crate::error::Result;
use crate::graph::{CodeGraph, Direction, EdgeId, EdgeType, NodeId, NodeType, PropertyMap};
pub struct FunctionMetadata<'a> {
pub name: &'a str,
pub line_start: i64,
pub line_end: i64,
pub visibility: &'a str,
pub signature: &'a str,
pub is_async: bool,
pub is_test: bool,
}
pub fn add_file(graph: &mut CodeGraph, path: &str, language: &str) -> Result<NodeId> {
let props = PropertyMap::new()
.with("path", path)
.with("language", language);
graph.add_node(NodeType::CodeFile, props)
}
pub fn add_function(
graph: &mut CodeGraph,
file_id: NodeId,
name: &str,
line_start: i64,
line_end: i64,
) -> Result<NodeId> {
let props = PropertyMap::new()
.with("name", name)
.with("line_start", line_start)
.with("line_end", line_end);
let func_id = graph.add_node(NodeType::Function, props)?;
graph.add_edge(file_id, func_id, EdgeType::Contains, PropertyMap::new())?;
Ok(func_id)
}
pub fn add_function_with_metadata(
graph: &mut CodeGraph,
file_id: NodeId,
metadata: FunctionMetadata,
) -> Result<NodeId> {
let props = PropertyMap::new()
.with("name", metadata.name)
.with("line_start", metadata.line_start)
.with("line_end", metadata.line_end)
.with("visibility", metadata.visibility)
.with("signature", metadata.signature)
.with("is_async", metadata.is_async)
.with("is_test", metadata.is_test);
let func_id = graph.add_node(NodeType::Function, props)?;
graph.add_edge(file_id, func_id, EdgeType::Contains, PropertyMap::new())?;
Ok(func_id)
}
pub fn add_class(
graph: &mut CodeGraph,
file_id: NodeId,
name: &str,
line_start: i64,
line_end: i64,
) -> Result<NodeId> {
let props = PropertyMap::new()
.with("name", name)
.with("line_start", line_start)
.with("line_end", line_end);
let class_id = graph.add_node(NodeType::Class, props)?;
graph.add_edge(file_id, class_id, EdgeType::Contains, PropertyMap::new())?;
Ok(class_id)
}
pub fn add_method(
graph: &mut CodeGraph,
class_id: NodeId,
name: &str,
line_start: i64,
line_end: i64,
) -> Result<NodeId> {
let props = PropertyMap::new()
.with("name", name)
.with("line_start", line_start)
.with("line_end", line_end);
let method_id = graph.add_node(NodeType::Function, props)?;
graph.add_edge(class_id, method_id, EdgeType::Contains, PropertyMap::new())?;
Ok(method_id)
}
pub fn add_module(graph: &mut CodeGraph, name: &str, path: &str) -> Result<NodeId> {
let props = PropertyMap::new().with("name", name).with("path", path);
graph.add_node(NodeType::Module, props)
}
pub fn add_call(
graph: &mut CodeGraph,
caller_id: NodeId,
callee_id: NodeId,
line: i64,
) -> Result<EdgeId> {
let props = PropertyMap::new().with("line", line);
graph.add_edge(caller_id, callee_id, EdgeType::Calls, props)
}
pub fn add_import(
graph: &mut CodeGraph,
from_file_id: NodeId,
to_file_id: NodeId,
symbols: Vec<&str>,
) -> Result<EdgeId> {
let symbol_strings: Vec<String> = symbols.iter().map(|s| s.to_string()).collect();
let props = PropertyMap::new().with("symbols", symbol_strings);
graph.add_edge(from_file_id, to_file_id, EdgeType::Imports, props)
}
pub fn link_to_file(
graph: &mut CodeGraph,
container_id: NodeId,
contained_id: NodeId,
) -> Result<EdgeId> {
graph.add_edge(
container_id,
contained_id,
EdgeType::Contains,
PropertyMap::new(),
)
}
pub fn get_callers(graph: &CodeGraph, function_id: NodeId) -> Result<Vec<NodeId>> {
let incoming = graph.get_neighbors(function_id, Direction::Incoming)?;
let mut callers = Vec::new();
for neighbor_id in incoming {
let edges = graph.get_edges_between(neighbor_id, function_id)?;
for edge_id in edges {
let edge = graph.get_edge(edge_id)?;
if edge.edge_type == EdgeType::Calls {
callers.push(neighbor_id);
break;
}
}
}
Ok(callers)
}
pub fn get_callees(graph: &CodeGraph, function_id: NodeId) -> Result<Vec<NodeId>> {
let outgoing = graph.get_neighbors(function_id, Direction::Outgoing)?;
let mut callees = Vec::new();
for neighbor_id in outgoing {
let edges = graph.get_edges_between(function_id, neighbor_id)?;
for edge_id in edges {
let edge = graph.get_edge(edge_id)?;
if edge.edge_type == EdgeType::Calls {
callees.push(neighbor_id);
break;
}
}
}
Ok(callees)
}
pub fn get_functions_in_file(graph: &CodeGraph, file_id: NodeId) -> Result<Vec<NodeId>> {
let contained = graph.get_neighbors(file_id, Direction::Outgoing)?;
let mut functions = Vec::new();
for node_id in contained {
let node = graph.get_node(node_id)?;
if node.node_type == NodeType::Function {
functions.push(node_id);
}
}
Ok(functions)
}
pub fn get_file_dependencies(graph: &CodeGraph, file_id: NodeId) -> Result<Vec<NodeId>> {
let outgoing = graph.get_neighbors(file_id, Direction::Outgoing)?;
let mut dependencies = Vec::new();
for neighbor_id in outgoing {
let edges = graph.get_edges_between(file_id, neighbor_id)?;
for edge_id in edges {
let edge = graph.get_edge(edge_id)?;
if edge.edge_type == EdgeType::Imports || edge.edge_type == EdgeType::ImportsFrom {
dependencies.push(neighbor_id);
break;
}
}
}
Ok(dependencies)
}
pub fn get_file_dependents(graph: &CodeGraph, file_id: NodeId) -> Result<Vec<NodeId>> {
let incoming = graph.get_neighbors(file_id, Direction::Incoming)?;
let mut dependents = Vec::new();
for neighbor_id in incoming {
let edges = graph.get_edges_between(neighbor_id, file_id)?;
for edge_id in edges {
let edge = graph.get_edge(edge_id)?;
if edge.edge_type == EdgeType::Imports || edge.edge_type == EdgeType::ImportsFrom {
dependents.push(neighbor_id);
break;
}
}
}
Ok(dependents)
}
pub fn transitive_dependencies(
graph: &CodeGraph,
file_id: NodeId,
max_depth: Option<usize>,
) -> Result<Vec<NodeId>> {
use std::collections::{HashSet, VecDeque};
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut result = Vec::new();
visited.insert(file_id);
queue.push_back((file_id, 0));
while let Some((current, depth)) = queue.pop_front() {
if let Some(max) = max_depth {
if depth >= max {
continue;
}
}
let deps = get_file_dependencies(graph, current)?;
for dep_id in deps {
if !visited.contains(&dep_id) {
visited.insert(dep_id);
result.push(dep_id);
queue.push_back((dep_id, depth + 1));
}
}
}
Ok(result)
}
pub fn transitive_dependents(
graph: &CodeGraph,
file_id: NodeId,
max_depth: Option<usize>,
) -> Result<Vec<NodeId>> {
use std::collections::{HashSet, VecDeque};
let mut visited = HashSet::new();
let mut queue = VecDeque::new();
let mut result = Vec::new();
visited.insert(file_id);
queue.push_back((file_id, 0));
while let Some((current, depth)) = queue.pop_front() {
if let Some(max) = max_depth {
if depth >= max {
continue;
}
}
let dependents = get_file_dependents(graph, current)?;
for dependent_id in dependents {
if !visited.contains(&dependent_id) {
visited.insert(dependent_id);
result.push(dependent_id);
queue.push_back((dependent_id, depth + 1));
}
}
}
Ok(result)
}
pub fn call_chain(
graph: &CodeGraph,
from_func: NodeId,
to_func: NodeId,
max_depth: Option<usize>,
) -> Result<Vec<Vec<NodeId>>> {
graph.find_all_paths(from_func, to_func, max_depth)
}
pub fn circular_deps(graph: &CodeGraph) -> Result<Vec<Vec<NodeId>>> {
let sccs = graph.find_strongly_connected_components()?;
let mut file_cycles = Vec::new();
for scc in sccs {
let mut file_nodes = Vec::new();
for node_id in &scc {
if let Ok(node) = graph.get_node(*node_id) {
if node.node_type == NodeType::CodeFile {
file_nodes.push(*node_id);
}
}
}
if file_nodes.len() > 1 {
file_cycles.push(file_nodes);
}
}
Ok(file_cycles)
}