1use crate::simple_parser::{ParsedFile, Function, Class};
2use petgraph::{Graph, Directed, graph::NodeIndex};
3use serde::{Deserialize, Serialize};
4use std::collections::HashMap;
5use std::path::PathBuf;
6
7pub type DependencyGraph = Graph<Node, Edge, Directed>;
8
9#[derive(Debug, Clone, Serialize, Deserialize)]
10pub struct Node {
11 pub id: String,
12 pub node_type: NodeType,
13 pub file_path: PathBuf,
14 pub line_number: usize,
15 pub metadata: NodeMetadata,
16}
17
18#[derive(Debug, Clone, Serialize, Deserialize)]
19pub enum NodeType {
20 File,
21 Module,
22 Function,
23 Class,
24 Variable,
25 Import,
26 Export,
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize)]
30pub struct NodeMetadata {
31 pub name: String,
32 pub language: Option<String>,
33 pub size: Option<u64>,
34 pub complexity: Option<usize>,
35 pub parameters: Vec<String>,
36 pub return_type: Option<String>,
37 pub is_async: bool,
38 pub is_exported: bool,
39 pub docstring: Option<String>,
40}
41
42#[derive(Debug, Clone, Serialize, Deserialize)]
43pub struct Edge {
44 pub edge_type: EdgeType,
45 pub weight: f64,
46 pub metadata: EdgeMetadata,
47}
48
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub enum EdgeType {
51 Imports,
52 Calls,
53 Extends,
54 Implements,
55 Contains,
56 References,
57 DependsOn,
58}
59
60#[derive(Debug, Clone, Serialize, Deserialize)]
61pub struct EdgeMetadata {
62 pub call_count: usize,
63 pub is_direct: bool,
64 pub line_numbers: Vec<usize>,
65}
66
67pub struct GraphBuilder {
68 graph: DependencyGraph,
69 node_map: HashMap<String, NodeIndex>,
70 file_nodes: HashMap<PathBuf, NodeIndex>,
71}
72
73impl GraphBuilder {
74 pub fn new() -> Self {
75 Self {
76 graph: Graph::new(),
77 node_map: HashMap::new(),
78 file_nodes: HashMap::new(),
79 }
80 }
81
82 pub fn build_graph(&mut self, parsed_files: &[ParsedFile]) -> &DependencyGraph {
83 for parsed_file in parsed_files {
84 self.add_file_node(parsed_file);
85 self.add_imports(parsed_file);
86 self.add_functions(parsed_file);
87 self.add_classes(parsed_file);
88 }
89
90 self.add_call_relationships(parsed_files);
91 &self.graph
92 }
93
94 fn add_file_node(&mut self, parsed_file: &ParsedFile) {
95 let node_id = format!("file:{}", parsed_file.file_info.path.display());
96
97 let node = Node {
98 id: node_id.clone(),
99 node_type: NodeType::File,
100 file_path: parsed_file.file_info.path.clone(),
101 line_number: 1,
102 metadata: NodeMetadata {
103 name: parsed_file.file_info.path
104 .file_name()
105 .and_then(|n| n.to_str())
106 .unwrap_or("unknown")
107 .to_string(),
108 language: parsed_file.file_info.language.clone(),
109 size: Some(parsed_file.file_info.size),
110 complexity: Some(self.calculate_file_complexity(parsed_file)),
111 parameters: Vec::new(),
112 return_type: None,
113 is_async: false,
114 is_exported: false,
115 docstring: None,
116 },
117 };
118
119 let node_index = self.graph.add_node(node);
120 self.node_map.insert(node_id, node_index);
121 self.file_nodes.insert(parsed_file.file_info.path.clone(), node_index);
122 }
123
124 fn add_imports(&mut self, parsed_file: &ParsedFile) {
125 let file_node = self.file_nodes[&parsed_file.file_info.path];
126
127 for import in &parsed_file.imports {
128 let import_id = format!("import:{}:{}", parsed_file.file_info.path.display(), import.module);
129
130 let node = Node {
131 id: import_id.clone(),
132 node_type: NodeType::Import,
133 file_path: parsed_file.file_info.path.clone(),
134 line_number: import.line_number,
135 metadata: NodeMetadata {
136 name: import.module.clone(),
137 language: parsed_file.file_info.language.clone(),
138 size: None,
139 complexity: None,
140 parameters: import.items.clone(),
141 return_type: None,
142 is_async: false,
143 is_exported: false,
144 docstring: None,
145 },
146 };
147
148 let import_node = self.graph.add_node(node);
149 self.node_map.insert(import_id, import_node);
150
151 let edge = Edge {
152 edge_type: EdgeType::Contains,
153 weight: 1.0,
154 metadata: EdgeMetadata {
155 call_count: 1,
156 is_direct: true,
157 line_numbers: vec![import.line_number],
158 },
159 };
160
161 self.graph.add_edge(file_node, import_node, edge);
162 }
163 }
164
165 fn add_functions(&mut self, parsed_file: &ParsedFile) {
166 let file_node = self.file_nodes[&parsed_file.file_info.path];
167
168 for function in &parsed_file.functions {
169 let function_id = format!("function:{}:{}", parsed_file.file_info.path.display(), function.name);
170
171 let node = Node {
172 id: function_id.clone(),
173 node_type: NodeType::Function,
174 file_path: parsed_file.file_info.path.clone(),
175 line_number: function.line_number,
176 metadata: NodeMetadata {
177 name: function.name.clone(),
178 language: parsed_file.file_info.language.clone(),
179 size: None,
180 complexity: Some(self.calculate_function_complexity(function)),
181 parameters: function.parameters.clone(),
182 return_type: function.return_type.clone(),
183 is_async: function.is_async,
184 is_exported: self.is_function_exported(parsed_file, function),
185 docstring: None,
186 },
187 };
188
189 let function_node = self.graph.add_node(node);
190 self.node_map.insert(function_id, function_node);
191
192 let edge = Edge {
193 edge_type: EdgeType::Contains,
194 weight: 1.0,
195 metadata: EdgeMetadata {
196 call_count: 1,
197 is_direct: true,
198 line_numbers: vec![function.line_number],
199 },
200 };
201
202 self.graph.add_edge(file_node, function_node, edge);
203 }
204 }
205
206 fn add_classes(&mut self, parsed_file: &ParsedFile) {
207 let file_node = self.file_nodes[&parsed_file.file_info.path];
208
209 for class in &parsed_file.classes {
210 let class_id = format!("class:{}:{}", parsed_file.file_info.path.display(), class.name);
211
212 let node = Node {
213 id: class_id.clone(),
214 node_type: NodeType::Class,
215 file_path: parsed_file.file_info.path.clone(),
216 line_number: class.line_number,
217 metadata: NodeMetadata {
218 name: class.name.clone(),
219 language: parsed_file.file_info.language.clone(),
220 size: None,
221 complexity: Some(self.calculate_class_complexity(class)),
222 parameters: Vec::new(),
223 return_type: None,
224 is_async: false,
225 is_exported: self.is_class_exported(parsed_file, class),
226 docstring: None,
227 },
228 };
229
230 let class_node = self.graph.add_node(node);
231 self.node_map.insert(class_id, class_node);
232
233 let edge = Edge {
234 edge_type: EdgeType::Contains,
235 weight: 1.0,
236 metadata: EdgeMetadata {
237 call_count: 1,
238 is_direct: true,
239 line_numbers: vec![class.line_number],
240 },
241 };
242
243 self.graph.add_edge(file_node, class_node, edge);
244
245 for method in &class.methods {
246 let method_id = format!("method:{}:{}:{}", parsed_file.file_info.path.display(), class.name, method.name);
247
248 let method_node_data = Node {
249 id: method_id.clone(),
250 node_type: NodeType::Function,
251 file_path: parsed_file.file_info.path.clone(),
252 line_number: method.line_number,
253 metadata: NodeMetadata {
254 name: format!("{}.{}", class.name, method.name),
255 language: parsed_file.file_info.language.clone(),
256 size: None,
257 complexity: Some(self.calculate_function_complexity(method)),
258 parameters: method.parameters.clone(),
259 return_type: method.return_type.clone(),
260 is_async: method.is_async,
261 is_exported: false,
262 docstring: None,
263 },
264 };
265
266 let method_node = self.graph.add_node(method_node_data);
267 self.node_map.insert(method_id, method_node);
268
269 let method_edge = Edge {
270 edge_type: EdgeType::Contains,
271 weight: 1.0,
272 metadata: EdgeMetadata {
273 call_count: 1,
274 is_direct: true,
275 line_numbers: vec![method.line_number],
276 },
277 };
278
279 self.graph.add_edge(class_node, method_node, method_edge);
280 }
281 }
282 }
283
284 fn add_call_relationships(&mut self, parsed_files: &[ParsedFile]) {
285 for parsed_file in parsed_files {
286 for import in &parsed_file.imports {
287 if let Some(target_file) = self.find_imported_file(parsed_files, &import.module) {
288 if let Some(&import_node) = self.node_map.get(&format!("import:{}:{}", parsed_file.file_info.path.display(), import.module)) {
289 if let Some(&target_node) = self.file_nodes.get(&target_file.file_info.path) {
290 let edge = Edge {
291 edge_type: EdgeType::DependsOn,
292 weight: 1.0,
293 metadata: EdgeMetadata {
294 call_count: 1,
295 is_direct: true,
296 line_numbers: vec![import.line_number],
297 },
298 };
299
300 self.graph.add_edge(import_node, target_node, edge);
301 }
302 }
303 }
304 }
305 }
306 }
307
308 fn find_imported_file<'a>(&self, parsed_files: &'a [ParsedFile], module_name: &str) -> Option<&'a ParsedFile> {
309 parsed_files.iter().find(|f| {
310 f.file_info.path
311 .file_stem()
312 .and_then(|s| s.to_str())
313 .map(|s| s == module_name)
314 .unwrap_or(false)
315 })
316 }
317
318 fn calculate_file_complexity(&self, parsed_file: &ParsedFile) -> usize {
319 parsed_file.functions.len() + parsed_file.classes.len() + parsed_file.imports.len()
320 }
321
322 fn calculate_function_complexity(&self, function: &Function) -> usize {
323 function.parameters.len() + if function.is_async { 2 } else { 1 }
324 }
325
326 fn calculate_class_complexity(&self, class: &Class) -> usize {
327 class.methods.len() + class.implements.len() + if class.extends.is_some() { 1 } else { 0 }
328 }
329
330 fn is_function_exported(&self, parsed_file: &ParsedFile, function: &Function) -> bool {
331 parsed_file.exports.iter().any(|e| e.name == function.name)
332 }
333
334 fn is_class_exported(&self, parsed_file: &ParsedFile, class: &Class) -> bool {
335 parsed_file.exports.iter().any(|e| e.name == class.name)
336 }
337
338 pub fn get_graph(&self) -> &DependencyGraph {
339 &self.graph
340 }
341
342 pub fn get_node_map(&self) -> &HashMap<String, NodeIndex> {
343 &self.node_map
344 }
345
346 pub fn analyze_dependencies(&self) -> DependencyAnalysis {
347 let total_nodes = self.graph.node_count();
348 let total_edges = self.graph.edge_count();
349
350 let mut node_types = HashMap::new();
351 let mut edge_types = HashMap::new();
352 let strongly_connected_components = 0;
353
354 for node_weight in self.graph.node_weights() {
355 *node_types.entry(format!("{:?}", node_weight.node_type)).or_insert(0) += 1;
356 }
357
358 for edge_weight in self.graph.edge_weights() {
359 *edge_types.entry(format!("{:?}", edge_weight.edge_type)).or_insert(0) += 1;
360 }
361
362 DependencyAnalysis {
363 total_nodes,
364 total_edges,
365 node_types,
366 edge_types,
367 strongly_connected_components,
368 avg_degree: if total_nodes > 0 { total_edges as f64 / total_nodes as f64 } else { 0.0 },
369 }
370 }
371}
372
373#[derive(Debug, Clone, Serialize, Deserialize)]
374pub struct DependencyAnalysis {
375 pub total_nodes: usize,
376 pub total_edges: usize,
377 pub node_types: HashMap<String, usize>,
378 pub edge_types: HashMap<String, usize>,
379 pub strongly_connected_components: usize,
380 pub avg_degree: f64,
381}
382
383impl DependencyAnalysis {
384 pub fn print_summary(&self) {
385 println!("Dependency Graph Analysis:");
386 println!(" Total nodes: {}", self.total_nodes);
387 println!(" Total edges: {}", self.total_edges);
388 println!(" Average degree: {:.2}", self.avg_degree);
389
390 println!(" Node types:");
391 for (node_type, count) in &self.node_types {
392 println!(" {}: {}", node_type, count);
393 }
394
395 println!(" Edge types:");
396 for (edge_type, count) in &self.edge_types {
397 println!(" {}: {}", edge_type, count);
398 }
399 }
400}