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 let mut type_names: Vec<&String> = types.iter().collect();
70 type_names.sort_unstable();
71
72 for type_name in type_names {
73 if !visited.contains(type_name) {
74 self.topological_visit(type_name, &mut sorted, &mut visited, &mut visiting);
75 }
76 }
77
78 sorted
79 }
80
81 fn topological_visit(
83 &self,
84 type_name: &str,
85 sorted: &mut Vec<String>,
86 visited: &mut HashSet<String>,
87 visiting: &mut HashSet<String>,
88 ) {
89 if visiting.contains(type_name) {
91 eprintln!(
92 "Warning: Circular dependency detected involving type: {}",
93 type_name
94 );
95 return;
96 }
97
98 if visited.contains(type_name) {
99 return;
100 }
101
102 visiting.insert(type_name.to_string());
103
104 if let Some(deps) = self.dependencies.get(type_name) {
106 let mut sorted_deps: Vec<&String> = deps.iter().collect();
107 sorted_deps.sort_unstable();
108
109 for dep in sorted_deps {
110 self.topological_visit(dep, sorted, visited, visiting);
111 }
112 }
113
114 visiting.remove(type_name);
115 visited.insert(type_name.to_string());
116 sorted.push(type_name.to_string());
117 }
118
119 fn sorted_commands<'a>(&self, commands: &'a [CommandInfo]) -> Vec<&'a CommandInfo> {
120 let mut sorted: Vec<_> = commands.iter().collect();
121 sorted.sort_by(|a, b| {
122 a.name
123 .cmp(&b.name)
124 .then_with(|| a.file_path.cmp(&b.file_path))
125 .then_with(|| a.line_number.cmp(&b.line_number))
126 });
127 sorted
128 }
129
130 fn sorted_resolved_type_names(&self) -> Vec<&String> {
131 let mut names: Vec<_> = self.resolved_types.keys().collect();
132 names.sort_unstable();
133 names
134 }
135
136 fn sorted_dependencies_for(&self, type_name: &str) -> Vec<&String> {
137 let mut deps: Vec<_> = self
138 .dependencies
139 .get(type_name)
140 .map(|deps| deps.iter().collect())
141 .unwrap_or_default();
142 deps.sort_unstable();
143 deps
144 }
145
146 fn sorted_dependency_owners(&self) -> Vec<&String> {
147 let mut owners: Vec<_> = self.dependencies.keys().collect();
148 owners.sort_unstable();
149 owners
150 }
151
152 pub fn visualize_dependencies(&self, entry_commands: &[crate::models::CommandInfo]) -> String {
154 let mut output = String::new();
155 output.push_str("š Type Dependency Graph\n");
156 output.push_str("======================\n\n");
157
158 output.push_str("š Command Entry Points:\n");
160 for cmd in self.sorted_commands(entry_commands) {
161 output.push_str(&format!(
162 "⢠{} ({}:{})\n",
163 cmd.name, cmd.file_path, cmd.line_number
164 ));
165
166 for param in &cmd.parameters {
168 output.push_str(&format!(" āā {}: {}\n", param.name, param.rust_type));
169 }
170
171 output.push_str(&format!(" āā returns: {}\n", cmd.return_type));
173 }
174
175 output.push_str("\nšļø Discovered Types:\n");
176 for type_name in self.sorted_resolved_type_names() {
177 let struct_info = &self.resolved_types[type_name];
178 let type_kind = if struct_info.is_enum {
179 "enum"
180 } else {
181 "struct"
182 };
183 output.push_str(&format!(
184 "⢠{} ({}) - {} fields - defined in {}\n",
185 type_name,
186 type_kind,
187 struct_info.fields.len(),
188 struct_info.file_path
189 ));
190
191 let deps_list: Vec<String> = self
193 .sorted_dependencies_for(type_name)
194 .into_iter()
195 .cloned()
196 .collect();
197 if !deps_list.is_empty() {
198 output.push_str(&format!(" āā depends on: {}\n", deps_list.join(", ")));
199 }
200 }
201
202 output.push_str("\nš Dependency Chains:\n");
204 for type_name in self.sorted_resolved_type_names() {
205 self.show_dependency_chain(type_name, &mut output, 0);
206 }
207
208 output.push_str(&format!(
209 "\nš Summary:\n⢠{} commands analyzed\n⢠{} types discovered\n⢠{} files with type definitions\n",
210 entry_commands.len(),
211 self.resolved_types.len(),
212 self.type_definitions.len()
213 ));
214
215 output
216 }
217
218 fn show_dependency_chain(&self, type_name: &str, output: &mut String, indent: usize) {
220 let indent_str = " ".repeat(indent);
221 output.push_str(&format!("{}āā {}\n", indent_str, type_name));
222
223 for dep in self.sorted_dependencies_for(type_name) {
224 if indent < 3 {
225 self.show_dependency_chain(dep, output, indent + 1);
227 }
228 }
229 }
230
231 pub fn generate_dot_graph(&self, commands: &[CommandInfo]) -> String {
233 let mut output = String::new();
234 output.push_str("digraph Dependencies {\n");
235 output.push_str(" rankdir=LR;\n");
236 output.push_str(" node [shape=box];\n");
237 output.push('\n');
238
239 for command in self.sorted_commands(commands) {
241 output.push_str(&format!(
242 " \"{}\" [color=blue, style=filled, fillcolor=lightblue];\n",
243 command.name
244 ));
245 }
246
247 for type_name in self.sorted_resolved_type_names() {
249 output.push_str(&format!(" \"{}\" [color=green];\n", type_name));
250 }
251
252 for command in self.sorted_commands(commands) {
254 for param in &command.parameters {
255 if self.resolved_types.contains_key(¶m.rust_type) {
256 output.push_str(&format!(
257 " \"{}\" -> \"{}\" [label=\"param\"];\n",
258 command.name, param.rust_type
259 ));
260 }
261 }
262 if self.resolved_types.contains_key(&command.return_type) {
263 output.push_str(&format!(
264 " \"{}\" -> \"{}\" [label=\"return\"];\n",
265 command.name, command.return_type
266 ));
267 }
268 }
269
270 for type_name in self.sorted_dependency_owners() {
272 for dep in self.sorted_dependencies_for(type_name) {
273 output.push_str(&format!(" \"{}\" -> \"{}\";\n", type_name, dep));
274 }
275 }
276
277 output.push_str("}\n");
278 output
279 }
280}
281
282#[cfg(test)]
283mod tests {
284 use super::*;
285
286 fn create_test_struct(name: &str, file: &str) -> StructInfo {
287 StructInfo {
288 name: name.to_string(),
289 fields: vec![],
290 file_path: file.to_string(),
291 is_enum: false,
292 serde_rename_all: None,
293 serde_tag: None,
294 enum_variants: None,
295 }
296 }
297
298 fn create_test_command(name: &str, file: &str, line: usize) -> CommandInfo {
299 CommandInfo::new_for_test(name, file, line, vec![], "String", false, vec![])
300 }
301
302 #[test]
303 fn test_new_graph() {
304 let graph = TypeDependencyGraph::new();
305 assert!(graph.type_definitions.is_empty());
306 assert!(graph.dependencies.is_empty());
307 assert!(graph.resolved_types.is_empty());
308 }
309
310 #[test]
311 fn test_default_impl() {
312 let graph = TypeDependencyGraph::default();
313 assert!(graph.type_definitions.is_empty());
314 }
315
316 #[test]
317 fn test_add_type_definition() {
318 let mut graph = TypeDependencyGraph::new();
319 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
320
321 assert!(graph.has_type_definition("User"));
322 assert_eq!(
323 graph.get_type_definition_path("User"),
324 Some(&PathBuf::from("user.rs"))
325 );
326 }
327
328 #[test]
329 fn test_add_multiple_type_definitions() {
330 let mut graph = TypeDependencyGraph::new();
331 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
332 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
333
334 assert!(graph.has_type_definition("User"));
335 assert!(graph.has_type_definition("Post"));
336 assert_eq!(graph.type_definitions.len(), 2);
337 }
338
339 #[test]
340 fn test_has_type_definition() {
341 let mut graph = TypeDependencyGraph::new();
342 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
343
344 assert!(graph.has_type_definition("User"));
345 assert!(!graph.has_type_definition("Post"));
346 }
347
348 #[test]
349 fn test_add_dependency() {
350 let mut graph = TypeDependencyGraph::new();
351 graph.add_dependency("Post".to_string(), "User".to_string());
352
353 let deps = graph.get_dependencies("Post");
354 assert!(deps.is_some());
355 assert!(deps.unwrap().contains("User"));
356 }
357
358 #[test]
359 fn test_add_multiple_dependencies_to_same_type() {
360 let mut graph = TypeDependencyGraph::new();
361 graph.add_dependency("Post".to_string(), "User".to_string());
362 graph.add_dependency("Post".to_string(), "Category".to_string());
363
364 let deps = graph.get_dependencies("Post").unwrap();
365 assert_eq!(deps.len(), 2);
366 assert!(deps.contains("User"));
367 assert!(deps.contains("Category"));
368 }
369
370 #[test]
371 fn test_add_dependencies_set() {
372 let mut graph = TypeDependencyGraph::new();
373 let mut deps = HashSet::new();
374 deps.insert("User".to_string());
375 deps.insert("Category".to_string());
376
377 graph.add_dependencies("Post".to_string(), deps);
378
379 let result_deps = graph.get_dependencies("Post").unwrap();
380 assert_eq!(result_deps.len(), 2);
381 assert!(result_deps.contains("User"));
382 assert!(result_deps.contains("Category"));
383 }
384
385 #[test]
386 fn test_get_dependencies_none() {
387 let graph = TypeDependencyGraph::new();
388 assert!(graph.get_dependencies("NonExistent").is_none());
389 }
390
391 #[test]
392 fn test_add_resolved_type() {
393 let mut graph = TypeDependencyGraph::new();
394 let struct_info = create_test_struct("User", "user.rs");
395
396 graph.add_resolved_type("User".to_string(), struct_info);
397
398 assert!(graph.resolved_types.contains_key("User"));
399 assert_eq!(graph.resolved_types.len(), 1);
400 }
401
402 #[test]
403 fn test_get_resolved_types() {
404 let mut graph = TypeDependencyGraph::new();
405 let struct_info = create_test_struct("User", "user.rs");
406
407 graph.add_resolved_type("User".to_string(), struct_info);
408
409 let resolved = graph.get_resolved_types();
410 assert_eq!(resolved.len(), 1);
411 assert!(resolved.contains_key("User"));
412 }
413
414 mod topological_sort {
416 use super::*;
417
418 #[test]
419 fn test_sort_single_type() {
420 let graph = TypeDependencyGraph::new();
421 let mut types = HashSet::new();
422 types.insert("User".to_string());
423
424 let sorted = graph.topological_sort_types(&types);
425 assert_eq!(sorted, vec!["User"]);
426 }
427
428 #[test]
429 fn test_sort_independent_types() {
430 let graph = TypeDependencyGraph::new();
431 let mut types = HashSet::new();
432 types.insert("User".to_string());
433 types.insert("Post".to_string());
434
435 let sorted = graph.topological_sort_types(&types);
436 assert_eq!(sorted, vec!["Post", "User"]);
437 }
438
439 #[test]
440 fn test_sort_linear_dependency() {
441 let mut graph = TypeDependencyGraph::new();
442 graph.add_dependency("Post".to_string(), "User".to_string());
444
445 let mut types = HashSet::new();
446 types.insert("User".to_string());
447 types.insert("Post".to_string());
448
449 let sorted = graph.topological_sort_types(&types);
450 assert_eq!(sorted, vec!["User", "Post"]);
452 }
453
454 #[test]
455 fn test_sort_diamond_dependency() {
456 let mut graph = TypeDependencyGraph::new();
457 graph.add_dependency("D".to_string(), "B".to_string());
461 graph.add_dependency("D".to_string(), "C".to_string());
462 graph.add_dependency("B".to_string(), "A".to_string());
463 graph.add_dependency("C".to_string(), "A".to_string());
464
465 let mut types = HashSet::new();
466 types.insert("A".to_string());
467 types.insert("B".to_string());
468 types.insert("C".to_string());
469 types.insert("D".to_string());
470
471 let sorted = graph.topological_sort_types(&types);
472
473 assert_eq!(sorted[0], "A");
475 assert_eq!(sorted[3], "D");
477 assert_eq!(sorted, vec!["A", "B", "C", "D"]);
478 }
479
480 #[test]
481 fn test_sort_chain_dependency() {
482 let mut graph = TypeDependencyGraph::new();
483 graph.add_dependency("D".to_string(), "C".to_string());
485 graph.add_dependency("C".to_string(), "B".to_string());
486 graph.add_dependency("B".to_string(), "A".to_string());
487
488 let mut types = HashSet::new();
489 types.insert("A".to_string());
490 types.insert("B".to_string());
491 types.insert("C".to_string());
492 types.insert("D".to_string());
493
494 let sorted = graph.topological_sort_types(&types);
495 assert_eq!(sorted, vec!["A", "B", "C", "D"]);
496 }
497
498 #[test]
499 fn test_sort_circular_dependency() {
500 let mut graph = TypeDependencyGraph::new();
501 graph.add_dependency("A".to_string(), "B".to_string());
503 graph.add_dependency("B".to_string(), "C".to_string());
504 graph.add_dependency("C".to_string(), "A".to_string());
505
506 let mut types = HashSet::new();
507 types.insert("A".to_string());
508 types.insert("B".to_string());
509 types.insert("C".to_string());
510
511 let sorted = graph.topological_sort_types(&types);
513 assert_eq!(sorted.len(), 3);
515 }
516
517 #[test]
518 fn test_sort_self_dependency() {
519 let mut graph = TypeDependencyGraph::new();
520 graph.add_dependency("A".to_string(), "A".to_string());
522
523 let mut types = HashSet::new();
524 types.insert("A".to_string());
525
526 let sorted = graph.topological_sort_types(&types);
528 assert!(!sorted.is_empty());
529 }
530
531 #[test]
532 fn test_sort_empty_set() {
533 let graph = TypeDependencyGraph::new();
534 let types = HashSet::new();
535
536 let sorted = graph.topological_sort_types(&types);
537 assert!(sorted.is_empty());
538 }
539
540 #[test]
541 fn test_sort_type_with_missing_dependency() {
542 let mut graph = TypeDependencyGraph::new();
543 graph.add_dependency("Post".to_string(), "User".to_string());
545
546 let mut types = HashSet::new();
547 types.insert("Post".to_string());
548
549 let sorted = graph.topological_sort_types(&types);
550 assert_eq!(sorted.len(), 2);
553 assert_eq!(sorted, vec!["User", "Post"]);
554 }
555 }
556
557 #[test]
559 fn test_full_graph_workflow() {
560 let mut graph = TypeDependencyGraph::new();
561
562 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
564 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
565
566 graph.add_dependency("Post".to_string(), "User".to_string());
568
569 graph.add_resolved_type("User".to_string(), create_test_struct("User", "user.rs"));
571 graph.add_resolved_type("Post".to_string(), create_test_struct("Post", "post.rs"));
572
573 assert!(graph.has_type_definition("User"));
575 assert!(graph.has_type_definition("Post"));
576 assert_eq!(graph.get_resolved_types().len(), 2);
577
578 let deps = graph.get_dependencies("Post");
579 assert!(deps.is_some());
580 assert!(deps.unwrap().contains("User"));
581
582 let mut types = HashSet::new();
584 types.insert("User".to_string());
585 types.insert("Post".to_string());
586 let sorted = graph.topological_sort_types(&types);
587 assert_eq!(sorted, vec!["User", "Post"]);
588 }
589
590 #[test]
591 fn test_visualize_dependencies_is_deterministic() {
592 let mut graph1 = TypeDependencyGraph::new();
593 graph1.add_resolved_type("Beta".to_string(), create_test_struct("Beta", "beta.rs"));
594 graph1.add_resolved_type("Alpha".to_string(), create_test_struct("Alpha", "alpha.rs"));
595 graph1.add_dependency("Beta".to_string(), "Delta".to_string());
596 graph1.add_dependency("Beta".to_string(), "Gamma".to_string());
597 graph1.add_dependency("Alpha".to_string(), "Gamma".to_string());
598
599 let mut graph2 = TypeDependencyGraph::new();
600 graph2.add_resolved_type("Alpha".to_string(), create_test_struct("Alpha", "alpha.rs"));
601 graph2.add_resolved_type("Beta".to_string(), create_test_struct("Beta", "beta.rs"));
602 graph2.add_dependency("Alpha".to_string(), "Gamma".to_string());
603 graph2.add_dependency("Beta".to_string(), "Gamma".to_string());
604 graph2.add_dependency("Beta".to_string(), "Delta".to_string());
605
606 let commands1 = vec![
607 create_test_command("beta_command", "beta.rs", 20),
608 create_test_command("alpha_command", "alpha.rs", 10),
609 ];
610 let commands2 = vec![
611 create_test_command("alpha_command", "alpha.rs", 10),
612 create_test_command("beta_command", "beta.rs", 20),
613 ];
614
615 assert_eq!(
616 graph1.visualize_dependencies(&commands1),
617 graph2.visualize_dependencies(&commands2)
618 );
619 }
620
621 #[test]
622 fn test_generate_dot_graph_is_deterministic() {
623 let mut graph1 = TypeDependencyGraph::new();
624 graph1.add_resolved_type("Beta".to_string(), create_test_struct("Beta", "beta.rs"));
625 graph1.add_resolved_type("Alpha".to_string(), create_test_struct("Alpha", "alpha.rs"));
626 graph1.add_dependency("Beta".to_string(), "Delta".to_string());
627 graph1.add_dependency("Beta".to_string(), "Gamma".to_string());
628 graph1.add_dependency("Alpha".to_string(), "Gamma".to_string());
629
630 let mut graph2 = TypeDependencyGraph::new();
631 graph2.add_resolved_type("Alpha".to_string(), create_test_struct("Alpha", "alpha.rs"));
632 graph2.add_resolved_type("Beta".to_string(), create_test_struct("Beta", "beta.rs"));
633 graph2.add_dependency("Alpha".to_string(), "Gamma".to_string());
634 graph2.add_dependency("Beta".to_string(), "Gamma".to_string());
635 graph2.add_dependency("Beta".to_string(), "Delta".to_string());
636
637 let commands1 = vec![
638 create_test_command("beta_command", "beta.rs", 20),
639 create_test_command("alpha_command", "alpha.rs", 10),
640 ];
641 let commands2 = vec![
642 create_test_command("alpha_command", "alpha.rs", 10),
643 create_test_command("beta_command", "beta.rs", 20),
644 ];
645
646 assert_eq!(
647 graph1.generate_dot_graph(&commands1),
648 graph2.generate_dot_graph(&commands2)
649 );
650 }
651}