1use crate::models::{CommandInfo, StructInfo};
2use std::collections::{HashMap, HashSet};
3use std::path::PathBuf;
4
5#[derive(Debug, Default)]
7pub struct TypeDependencyGraph {
8 pub type_definitions: HashMap<String, PathBuf>,
10 pub dependencies: HashMap<String, HashSet<String>>,
12 pub resolved_types: HashMap<String, StructInfo>,
14}
15
16impl TypeDependencyGraph {
17 pub fn new() -> Self {
18 Self::default()
19 }
20
21 pub fn add_type_definition(&mut self, type_name: String, file_path: PathBuf) {
23 self.type_definitions.insert(type_name, file_path);
24 }
25
26 pub fn add_dependency(&mut self, dependent: String, dependency: String) {
28 self.dependencies
29 .entry(dependent)
30 .or_default()
31 .insert(dependency);
32 }
33
34 pub fn add_dependencies(&mut self, dependent: String, dependencies: HashSet<String>) {
36 self.dependencies.insert(dependent, dependencies);
37 }
38
39 pub fn add_resolved_type(&mut self, type_name: String, struct_info: StructInfo) {
41 self.resolved_types.insert(type_name, struct_info);
42 }
43
44 pub fn get_resolved_types(&self) -> &HashMap<String, StructInfo> {
46 &self.resolved_types
47 }
48
49 pub fn get_dependencies(&self, type_name: &str) -> Option<&HashSet<String>> {
51 self.dependencies.get(type_name)
52 }
53
54 pub fn has_type_definition(&self, type_name: &str) -> bool {
56 self.type_definitions.contains_key(type_name)
57 }
58
59 pub fn get_type_definition_path(&self, type_name: &str) -> Option<&PathBuf> {
61 self.type_definitions.get(type_name)
62 }
63
64 pub fn topological_sort_types(&self, types: &HashSet<String>) -> Vec<String> {
66 let mut sorted = Vec::new();
67 let mut visited = HashSet::new();
68 let mut visiting = HashSet::new();
69
70 for type_name in types {
71 if !visited.contains(type_name) {
72 self.topological_visit(type_name, &mut sorted, &mut visited, &mut visiting);
73 }
74 }
75
76 sorted
77 }
78
79 fn topological_visit(
81 &self,
82 type_name: &str,
83 sorted: &mut Vec<String>,
84 visited: &mut HashSet<String>,
85 visiting: &mut HashSet<String>,
86 ) {
87 if visiting.contains(type_name) {
89 eprintln!(
90 "Warning: Circular dependency detected involving type: {}",
91 type_name
92 );
93 return;
94 }
95
96 if visited.contains(type_name) {
97 return;
98 }
99
100 visiting.insert(type_name.to_string());
101
102 if let Some(deps) = self.dependencies.get(type_name) {
104 for dep in deps {
105 self.topological_visit(dep, sorted, visited, visiting);
106 }
107 }
108
109 visiting.remove(type_name);
110 visited.insert(type_name.to_string());
111 sorted.push(type_name.to_string());
112 }
113
114 pub fn visualize_dependencies(&self, entry_commands: &[crate::models::CommandInfo]) -> String {
116 let mut output = String::new();
117 output.push_str("š Type Dependency Graph\n");
118 output.push_str("======================\n\n");
119
120 output.push_str("š Command Entry Points:\n");
122 for cmd in entry_commands {
123 output.push_str(&format!(
124 "⢠{} ({}:{})\n",
125 cmd.name, cmd.file_path, cmd.line_number
126 ));
127
128 for param in &cmd.parameters {
130 output.push_str(&format!(" āā {}: {}\n", param.name, param.rust_type));
131 }
132
133 output.push_str(&format!(" āā returns: {}\n", cmd.return_type));
135 }
136
137 output.push_str("\nšļø Discovered Types:\n");
138 for (type_name, struct_info) in &self.resolved_types {
139 let type_kind = if struct_info.is_enum {
140 "enum"
141 } else {
142 "struct"
143 };
144 output.push_str(&format!(
145 "⢠{} ({}) - {} fields - defined in {}\n",
146 type_name,
147 type_kind,
148 struct_info.fields.len(),
149 struct_info.file_path
150 ));
151
152 if let Some(deps) = self.dependencies.get(type_name) {
154 if !deps.is_empty() {
155 let deps_list: Vec<String> = deps.iter().cloned().collect();
156 output.push_str(&format!(" āā depends on: {}\n", deps_list.join(", ")));
157 }
158 }
159 }
160
161 output.push_str("\nš Dependency Chains:\n");
163 for type_name in self.resolved_types.keys() {
164 self.show_dependency_chain(type_name, &mut output, 0);
165 }
166
167 output.push_str(&format!(
168 "\nš Summary:\n⢠{} commands analyzed\n⢠{} types discovered\n⢠{} files with type definitions\n",
169 entry_commands.len(),
170 self.resolved_types.len(),
171 self.type_definitions.len()
172 ));
173
174 output
175 }
176
177 fn show_dependency_chain(&self, type_name: &str, output: &mut String, indent: usize) {
179 let indent_str = " ".repeat(indent);
180 output.push_str(&format!("{}āā {}\n", indent_str, type_name));
181
182 if let Some(deps) = self.dependencies.get(type_name) {
183 for dep in deps {
184 if indent < 3 {
185 self.show_dependency_chain(dep, output, indent + 1);
187 }
188 }
189 }
190 }
191
192 pub fn generate_dot_graph(&self, commands: &[CommandInfo]) -> String {
194 let mut output = String::new();
195 output.push_str("digraph Dependencies {\n");
196 output.push_str(" rankdir=LR;\n");
197 output.push_str(" node [shape=box];\n");
198 output.push('\n');
199
200 for command in commands {
202 output.push_str(&format!(
203 " \"{}\" [color=blue, style=filled, fillcolor=lightblue];\n",
204 command.name
205 ));
206 }
207
208 for type_name in self.resolved_types.keys() {
210 output.push_str(&format!(" \"{}\" [color=green];\n", type_name));
211 }
212
213 for command in commands {
215 for param in &command.parameters {
216 if self.resolved_types.contains_key(¶m.rust_type) {
217 output.push_str(&format!(
218 " \"{}\" -> \"{}\" [label=\"param\"];\n",
219 command.name, param.rust_type
220 ));
221 }
222 }
223 if self.resolved_types.contains_key(&command.return_type) {
224 output.push_str(&format!(
225 " \"{}\" -> \"{}\" [label=\"return\"];\n",
226 command.name, command.return_type
227 ));
228 }
229 }
230
231 for (type_name, deps) in &self.dependencies {
233 for dep in deps {
234 output.push_str(&format!(" \"{}\" -> \"{}\";\n", type_name, dep));
235 }
236 }
237
238 output.push_str("}\n");
239 output
240 }
241}
242
243#[cfg(test)]
244mod tests {
245 use super::*;
246
247 fn create_test_struct(name: &str, file: &str) -> StructInfo {
248 StructInfo {
249 name: name.to_string(),
250 fields: vec![],
251 file_path: file.to_string(),
252 is_enum: false,
253 serde_rename_all: None,
254 serde_tag: None,
255 enum_variants: None,
256 }
257 }
258
259 #[test]
260 fn test_new_graph() {
261 let graph = TypeDependencyGraph::new();
262 assert!(graph.type_definitions.is_empty());
263 assert!(graph.dependencies.is_empty());
264 assert!(graph.resolved_types.is_empty());
265 }
266
267 #[test]
268 fn test_default_impl() {
269 let graph = TypeDependencyGraph::default();
270 assert!(graph.type_definitions.is_empty());
271 }
272
273 #[test]
274 fn test_add_type_definition() {
275 let mut graph = TypeDependencyGraph::new();
276 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
277
278 assert!(graph.has_type_definition("User"));
279 assert_eq!(
280 graph.get_type_definition_path("User"),
281 Some(&PathBuf::from("user.rs"))
282 );
283 }
284
285 #[test]
286 fn test_add_multiple_type_definitions() {
287 let mut graph = TypeDependencyGraph::new();
288 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
289 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
290
291 assert!(graph.has_type_definition("User"));
292 assert!(graph.has_type_definition("Post"));
293 assert_eq!(graph.type_definitions.len(), 2);
294 }
295
296 #[test]
297 fn test_has_type_definition() {
298 let mut graph = TypeDependencyGraph::new();
299 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
300
301 assert!(graph.has_type_definition("User"));
302 assert!(!graph.has_type_definition("Post"));
303 }
304
305 #[test]
306 fn test_add_dependency() {
307 let mut graph = TypeDependencyGraph::new();
308 graph.add_dependency("Post".to_string(), "User".to_string());
309
310 let deps = graph.get_dependencies("Post");
311 assert!(deps.is_some());
312 assert!(deps.unwrap().contains("User"));
313 }
314
315 #[test]
316 fn test_add_multiple_dependencies_to_same_type() {
317 let mut graph = TypeDependencyGraph::new();
318 graph.add_dependency("Post".to_string(), "User".to_string());
319 graph.add_dependency("Post".to_string(), "Category".to_string());
320
321 let deps = graph.get_dependencies("Post").unwrap();
322 assert_eq!(deps.len(), 2);
323 assert!(deps.contains("User"));
324 assert!(deps.contains("Category"));
325 }
326
327 #[test]
328 fn test_add_dependencies_set() {
329 let mut graph = TypeDependencyGraph::new();
330 let mut deps = HashSet::new();
331 deps.insert("User".to_string());
332 deps.insert("Category".to_string());
333
334 graph.add_dependencies("Post".to_string(), deps);
335
336 let result_deps = graph.get_dependencies("Post").unwrap();
337 assert_eq!(result_deps.len(), 2);
338 assert!(result_deps.contains("User"));
339 assert!(result_deps.contains("Category"));
340 }
341
342 #[test]
343 fn test_get_dependencies_none() {
344 let graph = TypeDependencyGraph::new();
345 assert!(graph.get_dependencies("NonExistent").is_none());
346 }
347
348 #[test]
349 fn test_add_resolved_type() {
350 let mut graph = TypeDependencyGraph::new();
351 let struct_info = create_test_struct("User", "user.rs");
352
353 graph.add_resolved_type("User".to_string(), struct_info);
354
355 assert!(graph.resolved_types.contains_key("User"));
356 assert_eq!(graph.resolved_types.len(), 1);
357 }
358
359 #[test]
360 fn test_get_resolved_types() {
361 let mut graph = TypeDependencyGraph::new();
362 let struct_info = create_test_struct("User", "user.rs");
363
364 graph.add_resolved_type("User".to_string(), struct_info);
365
366 let resolved = graph.get_resolved_types();
367 assert_eq!(resolved.len(), 1);
368 assert!(resolved.contains_key("User"));
369 }
370
371 mod topological_sort {
373 use super::*;
374
375 #[test]
376 fn test_sort_single_type() {
377 let graph = TypeDependencyGraph::new();
378 let mut types = HashSet::new();
379 types.insert("User".to_string());
380
381 let sorted = graph.topological_sort_types(&types);
382 assert_eq!(sorted, vec!["User"]);
383 }
384
385 #[test]
386 fn test_sort_independent_types() {
387 let graph = TypeDependencyGraph::new();
388 let mut types = HashSet::new();
389 types.insert("User".to_string());
390 types.insert("Post".to_string());
391
392 let sorted = graph.topological_sort_types(&types);
393 assert_eq!(sorted.len(), 2);
394 assert!(sorted.contains(&"User".to_string()));
395 assert!(sorted.contains(&"Post".to_string()));
396 }
397
398 #[test]
399 fn test_sort_linear_dependency() {
400 let mut graph = TypeDependencyGraph::new();
401 graph.add_dependency("Post".to_string(), "User".to_string());
403
404 let mut types = HashSet::new();
405 types.insert("User".to_string());
406 types.insert("Post".to_string());
407
408 let sorted = graph.topological_sort_types(&types);
409 assert_eq!(sorted, vec!["User", "Post"]);
411 }
412
413 #[test]
414 fn test_sort_diamond_dependency() {
415 let mut graph = TypeDependencyGraph::new();
416 graph.add_dependency("D".to_string(), "B".to_string());
420 graph.add_dependency("D".to_string(), "C".to_string());
421 graph.add_dependency("B".to_string(), "A".to_string());
422 graph.add_dependency("C".to_string(), "A".to_string());
423
424 let mut types = HashSet::new();
425 types.insert("A".to_string());
426 types.insert("B".to_string());
427 types.insert("C".to_string());
428 types.insert("D".to_string());
429
430 let sorted = graph.topological_sort_types(&types);
431
432 assert_eq!(sorted[0], "A");
434 assert_eq!(sorted[3], "D");
436 let b_pos = sorted.iter().position(|x| x == "B").unwrap();
438 let c_pos = sorted.iter().position(|x| x == "C").unwrap();
439 assert!(b_pos > 0 && b_pos < 3);
440 assert!(c_pos > 0 && c_pos < 3);
441 }
442
443 #[test]
444 fn test_sort_chain_dependency() {
445 let mut graph = TypeDependencyGraph::new();
446 graph.add_dependency("D".to_string(), "C".to_string());
448 graph.add_dependency("C".to_string(), "B".to_string());
449 graph.add_dependency("B".to_string(), "A".to_string());
450
451 let mut types = HashSet::new();
452 types.insert("A".to_string());
453 types.insert("B".to_string());
454 types.insert("C".to_string());
455 types.insert("D".to_string());
456
457 let sorted = graph.topological_sort_types(&types);
458 assert_eq!(sorted, vec!["A", "B", "C", "D"]);
459 }
460
461 #[test]
462 fn test_sort_circular_dependency() {
463 let mut graph = TypeDependencyGraph::new();
464 graph.add_dependency("A".to_string(), "B".to_string());
466 graph.add_dependency("B".to_string(), "C".to_string());
467 graph.add_dependency("C".to_string(), "A".to_string());
468
469 let mut types = HashSet::new();
470 types.insert("A".to_string());
471 types.insert("B".to_string());
472 types.insert("C".to_string());
473
474 let sorted = graph.topological_sort_types(&types);
476 assert_eq!(sorted.len(), 3);
478 }
479
480 #[test]
481 fn test_sort_self_dependency() {
482 let mut graph = TypeDependencyGraph::new();
483 graph.add_dependency("A".to_string(), "A".to_string());
485
486 let mut types = HashSet::new();
487 types.insert("A".to_string());
488
489 let sorted = graph.topological_sort_types(&types);
491 assert!(!sorted.is_empty());
492 }
493
494 #[test]
495 fn test_sort_empty_set() {
496 let graph = TypeDependencyGraph::new();
497 let types = HashSet::new();
498
499 let sorted = graph.topological_sort_types(&types);
500 assert!(sorted.is_empty());
501 }
502
503 #[test]
504 fn test_sort_type_with_missing_dependency() {
505 let mut graph = TypeDependencyGraph::new();
506 graph.add_dependency("Post".to_string(), "User".to_string());
508
509 let mut types = HashSet::new();
510 types.insert("Post".to_string());
511
512 let sorted = graph.topological_sort_types(&types);
513 assert_eq!(sorted.len(), 2);
516 assert_eq!(sorted, vec!["User", "Post"]);
517 }
518 }
519
520 #[test]
522 fn test_full_graph_workflow() {
523 let mut graph = TypeDependencyGraph::new();
524
525 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
527 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
528
529 graph.add_dependency("Post".to_string(), "User".to_string());
531
532 graph.add_resolved_type("User".to_string(), create_test_struct("User", "user.rs"));
534 graph.add_resolved_type("Post".to_string(), create_test_struct("Post", "post.rs"));
535
536 assert!(graph.has_type_definition("User"));
538 assert!(graph.has_type_definition("Post"));
539 assert_eq!(graph.get_resolved_types().len(), 2);
540
541 let deps = graph.get_dependencies("Post");
542 assert!(deps.is_some());
543 assert!(deps.unwrap().contains("User"));
544
545 let mut types = HashSet::new();
547 types.insert("User".to_string());
548 types.insert("Post".to_string());
549 let sorted = graph.topological_sort_types(&types);
550 assert_eq!(sorted, vec!["User", "Post"]);
551 }
552}