tauri_typegen/analysis/
dependency_graph.rs

1use crate::models::{CommandInfo, StructInfo};
2use std::collections::{HashMap, HashSet};
3use std::path::PathBuf;
4
5/// Dependency graph for lazy type resolution
6#[derive(Debug, Default)]
7pub struct TypeDependencyGraph {
8    /// Maps type name to the files where it's defined
9    pub type_definitions: HashMap<String, PathBuf>,
10    /// Maps type name to types it depends on
11    pub dependencies: HashMap<String, HashSet<String>>,
12    /// Maps type name to its resolved StructInfo
13    pub resolved_types: HashMap<String, StructInfo>,
14}
15
16impl TypeDependencyGraph {
17    pub fn new() -> Self {
18        Self::default()
19    }
20
21    /// Add a type definition to the graph
22    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    /// Add a dependency relationship between types
27    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    /// Add multiple dependencies for a type
35    pub fn add_dependencies(&mut self, dependent: String, dependencies: HashSet<String>) {
36        self.dependencies.insert(dependent, dependencies);
37    }
38
39    /// Add a resolved type to the graph
40    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    /// Get all resolved types
45    pub fn get_resolved_types(&self) -> &HashMap<String, StructInfo> {
46        &self.resolved_types
47    }
48
49    /// Get dependencies for a type
50    pub fn get_dependencies(&self, type_name: &str) -> Option<&HashSet<String>> {
51        self.dependencies.get(type_name)
52    }
53
54    /// Check if a type is defined in the graph
55    pub fn has_type_definition(&self, type_name: &str) -> bool {
56        self.type_definitions.contains_key(type_name)
57    }
58
59    /// Get the file path where a type is defined
60    pub fn get_type_definition_path(&self, type_name: &str) -> Option<&PathBuf> {
61        self.type_definitions.get(type_name)
62    }
63
64    /// Perform topological sort on the given types using the dependency graph
65    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    /// Recursive helper for topological sorting with cycle detection
80    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        // Check for cycles
88        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        // Visit dependencies first
103        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    /// Build visualization of the dependency graph
115    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        // Show command entry points
121        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            // Show parameters
129            for param in &cmd.parameters {
130                output.push_str(&format!(
131                    "  ā”œā”€ {}: {} → {}\n",
132                    param.name, param.rust_type, param.typescript_type
133                ));
134            }
135
136            // Show return type
137            output.push_str(&format!(
138                "  └─ returns: {} → {}\n",
139                cmd.return_type, cmd.return_type_ts
140            ));
141        }
142
143        output.push_str("\nšŸ—ļø  Discovered Types:\n");
144        for (type_name, struct_info) in &self.resolved_types {
145            let type_kind = if struct_info.is_enum {
146                "enum"
147            } else {
148                "struct"
149            };
150            output.push_str(&format!(
151                "• {} ({}) - {} fields - defined in {}\n",
152                type_name,
153                type_kind,
154                struct_info.fields.len(),
155                struct_info.file_path
156            ));
157
158            // Show dependencies
159            if let Some(deps) = self.dependencies.get(type_name) {
160                if !deps.is_empty() {
161                    let deps_list: Vec<String> = deps.iter().cloned().collect();
162                    output.push_str(&format!("  └─ depends on: {}\n", deps_list.join(", ")));
163                }
164            }
165        }
166
167        // Show dependency chains
168        output.push_str("\nšŸ”— Dependency Chains:\n");
169        for type_name in self.resolved_types.keys() {
170            self.show_dependency_chain(type_name, &mut output, 0);
171        }
172
173        output.push_str(&format!(
174            "\nšŸ“Š Summary:\n• {} commands analyzed\n• {} types discovered\n• {} files with type definitions\n",
175            entry_commands.len(),
176            self.resolved_types.len(),
177            self.type_definitions.len()
178        ));
179
180        output
181    }
182
183    /// Recursively show dependency chain for a type
184    fn show_dependency_chain(&self, type_name: &str, output: &mut String, indent: usize) {
185        let indent_str = "  ".repeat(indent);
186        output.push_str(&format!("{}ā”œā”€ {}\n", indent_str, type_name));
187
188        if let Some(deps) = self.dependencies.get(type_name) {
189            for dep in deps {
190                if indent < 3 {
191                    // Prevent too deep recursion in visualization
192                    self.show_dependency_chain(dep, output, indent + 1);
193                }
194            }
195        }
196    }
197
198    /// Generate a DOT graph representation of the dependency graph
199    pub fn generate_dot_graph(&self, commands: &[CommandInfo]) -> String {
200        let mut output = String::new();
201        output.push_str("digraph Dependencies {\n");
202        output.push_str("  rankdir=LR;\n");
203        output.push_str("  node [shape=box];\n");
204        output.push('\n');
205
206        // Add command nodes
207        for command in commands {
208            output.push_str(&format!(
209                "  \"{}\" [color=blue, style=filled, fillcolor=lightblue];\n",
210                command.name
211            ));
212        }
213
214        // Add type nodes
215        for type_name in self.resolved_types.keys() {
216            output.push_str(&format!("  \"{}\" [color=green];\n", type_name));
217        }
218
219        // Add edges from commands to their parameter/return types
220        for command in commands {
221            for param in &command.parameters {
222                if self.resolved_types.contains_key(&param.rust_type) {
223                    output.push_str(&format!(
224                        "  \"{}\" -> \"{}\" [label=\"param\"];\n",
225                        command.name, param.rust_type
226                    ));
227                }
228            }
229            if self.resolved_types.contains_key(&command.return_type) {
230                output.push_str(&format!(
231                    "  \"{}\" -> \"{}\" [label=\"return\"];\n",
232                    command.name, command.return_type
233                ));
234            }
235        }
236
237        // Add type dependency edges
238        for (type_name, deps) in &self.dependencies {
239            for dep in deps {
240                output.push_str(&format!("  \"{}\" -> \"{}\";\n", type_name, dep));
241            }
242        }
243
244        output.push_str("}\n");
245        output
246    }
247}