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 }
255 }
256
257 #[test]
258 fn test_new_graph() {
259 let graph = TypeDependencyGraph::new();
260 assert!(graph.type_definitions.is_empty());
261 assert!(graph.dependencies.is_empty());
262 assert!(graph.resolved_types.is_empty());
263 }
264
265 #[test]
266 fn test_default_impl() {
267 let graph = TypeDependencyGraph::default();
268 assert!(graph.type_definitions.is_empty());
269 }
270
271 #[test]
272 fn test_add_type_definition() {
273 let mut graph = TypeDependencyGraph::new();
274 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
275
276 assert!(graph.has_type_definition("User"));
277 assert_eq!(
278 graph.get_type_definition_path("User"),
279 Some(&PathBuf::from("user.rs"))
280 );
281 }
282
283 #[test]
284 fn test_add_multiple_type_definitions() {
285 let mut graph = TypeDependencyGraph::new();
286 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
287 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
288
289 assert!(graph.has_type_definition("User"));
290 assert!(graph.has_type_definition("Post"));
291 assert_eq!(graph.type_definitions.len(), 2);
292 }
293
294 #[test]
295 fn test_has_type_definition() {
296 let mut graph = TypeDependencyGraph::new();
297 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
298
299 assert!(graph.has_type_definition("User"));
300 assert!(!graph.has_type_definition("Post"));
301 }
302
303 #[test]
304 fn test_add_dependency() {
305 let mut graph = TypeDependencyGraph::new();
306 graph.add_dependency("Post".to_string(), "User".to_string());
307
308 let deps = graph.get_dependencies("Post");
309 assert!(deps.is_some());
310 assert!(deps.unwrap().contains("User"));
311 }
312
313 #[test]
314 fn test_add_multiple_dependencies_to_same_type() {
315 let mut graph = TypeDependencyGraph::new();
316 graph.add_dependency("Post".to_string(), "User".to_string());
317 graph.add_dependency("Post".to_string(), "Category".to_string());
318
319 let deps = graph.get_dependencies("Post").unwrap();
320 assert_eq!(deps.len(), 2);
321 assert!(deps.contains("User"));
322 assert!(deps.contains("Category"));
323 }
324
325 #[test]
326 fn test_add_dependencies_set() {
327 let mut graph = TypeDependencyGraph::new();
328 let mut deps = HashSet::new();
329 deps.insert("User".to_string());
330 deps.insert("Category".to_string());
331
332 graph.add_dependencies("Post".to_string(), deps);
333
334 let result_deps = graph.get_dependencies("Post").unwrap();
335 assert_eq!(result_deps.len(), 2);
336 assert!(result_deps.contains("User"));
337 assert!(result_deps.contains("Category"));
338 }
339
340 #[test]
341 fn test_get_dependencies_none() {
342 let graph = TypeDependencyGraph::new();
343 assert!(graph.get_dependencies("NonExistent").is_none());
344 }
345
346 #[test]
347 fn test_add_resolved_type() {
348 let mut graph = TypeDependencyGraph::new();
349 let struct_info = create_test_struct("User", "user.rs");
350
351 graph.add_resolved_type("User".to_string(), struct_info);
352
353 assert!(graph.resolved_types.contains_key("User"));
354 assert_eq!(graph.resolved_types.len(), 1);
355 }
356
357 #[test]
358 fn test_get_resolved_types() {
359 let mut graph = TypeDependencyGraph::new();
360 let struct_info = create_test_struct("User", "user.rs");
361
362 graph.add_resolved_type("User".to_string(), struct_info);
363
364 let resolved = graph.get_resolved_types();
365 assert_eq!(resolved.len(), 1);
366 assert!(resolved.contains_key("User"));
367 }
368
369 mod topological_sort {
371 use super::*;
372
373 #[test]
374 fn test_sort_single_type() {
375 let graph = TypeDependencyGraph::new();
376 let mut types = HashSet::new();
377 types.insert("User".to_string());
378
379 let sorted = graph.topological_sort_types(&types);
380 assert_eq!(sorted, vec!["User"]);
381 }
382
383 #[test]
384 fn test_sort_independent_types() {
385 let graph = TypeDependencyGraph::new();
386 let mut types = HashSet::new();
387 types.insert("User".to_string());
388 types.insert("Post".to_string());
389
390 let sorted = graph.topological_sort_types(&types);
391 assert_eq!(sorted.len(), 2);
392 assert!(sorted.contains(&"User".to_string()));
393 assert!(sorted.contains(&"Post".to_string()));
394 }
395
396 #[test]
397 fn test_sort_linear_dependency() {
398 let mut graph = TypeDependencyGraph::new();
399 graph.add_dependency("Post".to_string(), "User".to_string());
401
402 let mut types = HashSet::new();
403 types.insert("User".to_string());
404 types.insert("Post".to_string());
405
406 let sorted = graph.topological_sort_types(&types);
407 assert_eq!(sorted, vec!["User", "Post"]);
409 }
410
411 #[test]
412 fn test_sort_diamond_dependency() {
413 let mut graph = TypeDependencyGraph::new();
414 graph.add_dependency("D".to_string(), "B".to_string());
418 graph.add_dependency("D".to_string(), "C".to_string());
419 graph.add_dependency("B".to_string(), "A".to_string());
420 graph.add_dependency("C".to_string(), "A".to_string());
421
422 let mut types = HashSet::new();
423 types.insert("A".to_string());
424 types.insert("B".to_string());
425 types.insert("C".to_string());
426 types.insert("D".to_string());
427
428 let sorted = graph.topological_sort_types(&types);
429
430 assert_eq!(sorted[0], "A");
432 assert_eq!(sorted[3], "D");
434 let b_pos = sorted.iter().position(|x| x == "B").unwrap();
436 let c_pos = sorted.iter().position(|x| x == "C").unwrap();
437 assert!(b_pos > 0 && b_pos < 3);
438 assert!(c_pos > 0 && c_pos < 3);
439 }
440
441 #[test]
442 fn test_sort_chain_dependency() {
443 let mut graph = TypeDependencyGraph::new();
444 graph.add_dependency("D".to_string(), "C".to_string());
446 graph.add_dependency("C".to_string(), "B".to_string());
447 graph.add_dependency("B".to_string(), "A".to_string());
448
449 let mut types = HashSet::new();
450 types.insert("A".to_string());
451 types.insert("B".to_string());
452 types.insert("C".to_string());
453 types.insert("D".to_string());
454
455 let sorted = graph.topological_sort_types(&types);
456 assert_eq!(sorted, vec!["A", "B", "C", "D"]);
457 }
458
459 #[test]
460 fn test_sort_circular_dependency() {
461 let mut graph = TypeDependencyGraph::new();
462 graph.add_dependency("A".to_string(), "B".to_string());
464 graph.add_dependency("B".to_string(), "C".to_string());
465 graph.add_dependency("C".to_string(), "A".to_string());
466
467 let mut types = HashSet::new();
468 types.insert("A".to_string());
469 types.insert("B".to_string());
470 types.insert("C".to_string());
471
472 let sorted = graph.topological_sort_types(&types);
474 assert_eq!(sorted.len(), 3);
476 }
477
478 #[test]
479 fn test_sort_self_dependency() {
480 let mut graph = TypeDependencyGraph::new();
481 graph.add_dependency("A".to_string(), "A".to_string());
483
484 let mut types = HashSet::new();
485 types.insert("A".to_string());
486
487 let sorted = graph.topological_sort_types(&types);
489 assert!(!sorted.is_empty());
490 }
491
492 #[test]
493 fn test_sort_empty_set() {
494 let graph = TypeDependencyGraph::new();
495 let types = HashSet::new();
496
497 let sorted = graph.topological_sort_types(&types);
498 assert!(sorted.is_empty());
499 }
500
501 #[test]
502 fn test_sort_type_with_missing_dependency() {
503 let mut graph = TypeDependencyGraph::new();
504 graph.add_dependency("Post".to_string(), "User".to_string());
506
507 let mut types = HashSet::new();
508 types.insert("Post".to_string());
509
510 let sorted = graph.topological_sort_types(&types);
511 assert_eq!(sorted.len(), 2);
514 assert_eq!(sorted, vec!["User", "Post"]);
515 }
516 }
517
518 #[test]
520 fn test_full_graph_workflow() {
521 let mut graph = TypeDependencyGraph::new();
522
523 graph.add_type_definition("User".to_string(), PathBuf::from("user.rs"));
525 graph.add_type_definition("Post".to_string(), PathBuf::from("post.rs"));
526
527 graph.add_dependency("Post".to_string(), "User".to_string());
529
530 graph.add_resolved_type("User".to_string(), create_test_struct("User", "user.rs"));
532 graph.add_resolved_type("Post".to_string(), create_test_struct("Post", "post.rs"));
533
534 assert!(graph.has_type_definition("User"));
536 assert!(graph.has_type_definition("Post"));
537 assert_eq!(graph.get_resolved_types().len(), 2);
538
539 let deps = graph.get_dependencies("Post");
540 assert!(deps.is_some());
541 assert!(deps.unwrap().contains("User"));
542
543 let mut types = HashSet::new();
545 types.insert("User".to_string());
546 types.insert("Post".to_string());
547 let sorted = graph.topological_sort_types(&types);
548 assert_eq!(sorted, vec!["User", "Post"]);
549 }
550}