context_creator/core/semantic/
graph_builder.rs1use crate::core::semantic::dependency_types::{
7 DependencyEdgeType, DependencyNode as RichNode, FileAnalysisResult,
8};
9use crate::core::walker::FileInfo;
10use anyhow::Result;
11use petgraph::graph::{DiGraph, NodeIndex};
12use std::collections::HashMap;
13use std::path::{Path, PathBuf};
14
15pub struct GraphBuilder {
17 }
19
20impl GraphBuilder {
21 pub fn new() -> Self {
23 Self {}
24 }
25
26 pub fn build(
28 &self,
29 files: &[FileInfo],
30 ) -> Result<(
31 DiGraph<RichNode, DependencyEdgeType>,
32 HashMap<PathBuf, NodeIndex>,
33 )> {
34 let mut graph = DiGraph::new();
35 let mut node_map = HashMap::new();
36
37 for (index, file) in files.iter().enumerate() {
39 let rich_node = RichNode {
40 file_index: index,
41 path: file.path.clone(),
42 language: Self::detect_language(&file.path),
43 content_hash: None, file_size: file.size,
45 depth: 0,
46 };
47
48 let node_idx = graph.add_node(rich_node);
49 node_map.insert(file.path.clone(), node_idx);
51 }
52
53 Ok((graph, node_map))
54 }
55
56 pub fn add_edge(
58 &self,
59 graph: &mut DiGraph<RichNode, DependencyEdgeType>,
60 from: NodeIndex,
61 to: NodeIndex,
62 edge_type: DependencyEdgeType,
63 ) {
64 if from != to {
66 graph.add_edge(from, to, edge_type);
67 }
68 }
69
70 pub fn build_edges_from_imports(
72 &self,
73 graph: &mut DiGraph<RichNode, DependencyEdgeType>,
74 files: &[FileInfo],
75 node_map: &HashMap<PathBuf, NodeIndex>,
76 ) {
77 for file in files {
78 if let Some(&from_idx) = node_map.get(&file.path) {
79 for import_path in &file.imports {
80 if let Some(&to_idx) = node_map.get(import_path) {
81 let edge_type = DependencyEdgeType::Import {
82 symbols: Vec::new(), };
84 self.add_edge(graph, from_idx, to_idx, edge_type);
85 }
86 }
87 }
88 }
89 }
90
91 pub fn build_edges_from_analysis(
93 &self,
94 graph: &mut DiGraph<RichNode, DependencyEdgeType>,
95 analysis_results: &[FileAnalysisResult],
96 path_to_index: &HashMap<PathBuf, usize>,
97 node_map: &HashMap<PathBuf, NodeIndex>,
98 ) {
99 for result in analysis_results {
100 let file_index = result.file_index;
101
102 let source_path = path_to_index
104 .iter()
105 .find(|(_, &idx)| idx == file_index)
106 .map(|(path, _)| path.clone());
107
108 if let Some(source_path) = source_path {
109 if let Some(&from_idx) = node_map.get(&source_path) {
110 if let Some(hash) = result.content_hash {
112 graph[from_idx].content_hash = Some(hash);
113 }
114
115 for (import_path, edge_type) in &result.imports {
117 for (path, &to_idx) in node_map {
119 if path
120 .to_string_lossy()
121 .contains(&import_path.to_string_lossy().to_string())
122 {
123 self.add_edge(graph, from_idx, to_idx, edge_type.clone());
124 break;
125 }
126 }
127 }
128 }
129 }
130 }
131 }
132
133 fn detect_language(path: &Path) -> Option<String> {
135 path.extension()
136 .and_then(|ext| ext.to_str())
137 .map(|ext| match ext {
138 "rs" => "rust",
139 "py" => "python",
140 "js" | "mjs" => "javascript",
141 "ts" | "tsx" => "typescript",
142 "jsx" => "javascript",
143 "go" => "go",
144 "java" => "java",
145 "cpp" | "cc" | "cxx" => "cpp",
146 "c" => "c",
147 "rb" => "ruby",
148 "php" => "php",
149 "swift" => "swift",
150 "kt" => "kotlin",
151 "scala" => "scala",
152 "r" => "r",
153 "sh" | "bash" => "shell",
154 "yaml" | "yml" => "yaml",
155 "json" => "json",
156 "xml" => "xml",
157 "html" | "htm" => "html",
158 "css" | "scss" | "sass" => "css",
159 _ => ext,
160 })
161 .map(String::from)
162 }
163}
164
165impl Default for GraphBuilder {
166 fn default() -> Self {
167 Self::new()
168 }
169}
170
171#[cfg(test)]
172#[path = "graph_builder_tests.rs"]
173mod tests;