1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
//! Graph construction module for semantic analysis
//!
//! This module is responsible for building dependency graphs from file information.
//! It follows the Single Responsibility Principle by focusing solely on graph construction.
use crate::core::semantic::dependency_types::{
DependencyEdgeType, DependencyNode as RichNode, FileAnalysisResult,
};
use crate::core::walker::FileInfo;
use anyhow::Result;
use petgraph::graph::{DiGraph, NodeIndex};
use std::collections::HashMap;
use std::path::{Path, PathBuf};
/// Builder for constructing dependency graphs
pub struct GraphBuilder {
// Future: Could add configuration options here
}
impl GraphBuilder {
/// Create a new GraphBuilder
pub fn new() -> Self {
Self {}
}
/// Build a dependency graph from file information
pub fn build(
&self,
files: &[FileInfo],
) -> Result<(
DiGraph<RichNode, DependencyEdgeType>,
HashMap<PathBuf, NodeIndex>,
)> {
let mut graph = DiGraph::new();
let mut node_map = HashMap::new();
// Create nodes for each file
for (index, file) in files.iter().enumerate() {
let rich_node = RichNode {
file_index: index,
path: file.path.clone(),
language: Self::detect_language(&file.path),
content_hash: None, // Will be filled during analysis
file_size: file.size,
depth: 0,
};
let node_idx = graph.add_node(rich_node);
// Only store the last occurrence if there are duplicates
node_map.insert(file.path.clone(), node_idx);
}
Ok((graph, node_map))
}
/// Add a dependency edge to the graph
pub fn add_edge(
&self,
graph: &mut DiGraph<RichNode, DependencyEdgeType>,
from: NodeIndex,
to: NodeIndex,
edge_type: DependencyEdgeType,
) {
// Avoid self-loops
if from != to {
graph.add_edge(from, to, edge_type);
}
}
/// Build edges from file import information
pub fn build_edges_from_imports(
&self,
graph: &mut DiGraph<RichNode, DependencyEdgeType>,
files: &[FileInfo],
node_map: &HashMap<PathBuf, NodeIndex>,
) {
for file in files {
if let Some(&from_idx) = node_map.get(&file.path) {
for import_path in &file.imports {
if let Some(&to_idx) = node_map.get(import_path) {
let edge_type = DependencyEdgeType::Import {
symbols: Vec::new(), // Basic import without symbol information
};
self.add_edge(graph, from_idx, to_idx, edge_type);
}
}
}
}
}
/// Build edges from parallel analysis results
pub fn build_edges_from_analysis(
&self,
graph: &mut DiGraph<RichNode, DependencyEdgeType>,
analysis_results: &[FileAnalysisResult],
path_to_index: &HashMap<PathBuf, usize>,
node_map: &HashMap<PathBuf, NodeIndex>,
) {
for result in analysis_results {
let file_index = result.file_index;
// Find the source node
let source_path = path_to_index
.iter()
.find(|(_, &idx)| idx == file_index)
.map(|(path, _)| path.clone());
if let Some(source_path) = source_path {
if let Some(&from_idx) = node_map.get(&source_path) {
// Update node with content hash
if let Some(hash) = result.content_hash {
graph[from_idx].content_hash = Some(hash);
}
// Add import edges
for (import_path, edge_type) in &result.imports {
// Try to find the target in our node map
for (path, &to_idx) in node_map {
if path
.to_string_lossy()
.contains(&import_path.to_string_lossy().to_string())
{
self.add_edge(graph, from_idx, to_idx, edge_type.clone());
break;
}
}
}
}
}
}
}
/// Detect programming language from file extension
fn detect_language(path: &Path) -> Option<String> {
path.extension()
.and_then(|ext| ext.to_str())
.map(|ext| match ext {
"rs" => "rust",
"py" => "python",
"js" | "mjs" => "javascript",
"ts" | "tsx" => "typescript",
"jsx" => "javascript",
"go" => "go",
"java" => "java",
"cpp" | "cc" | "cxx" => "cpp",
"c" => "c",
"rb" => "ruby",
"php" => "php",
"swift" => "swift",
"kt" => "kotlin",
"scala" => "scala",
"r" => "r",
"sh" | "bash" => "shell",
"yaml" | "yml" => "yaml",
"json" => "json",
"xml" => "xml",
"html" | "htm" => "html",
"css" | "scss" | "sass" => "css",
_ => ext,
})
.map(String::from)
}
}
impl Default for GraphBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
#[path = "graph_builder_tests.rs"]
mod tests;