tauri_typegen/analysis/
dependency_graph.rs1use 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!(
131 " āā {}: {} ā {}\n",
132 param.name, param.rust_type, param.typescript_type
133 ));
134 }
135
136 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 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 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 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 self.show_dependency_chain(dep, output, indent + 1);
193 }
194 }
195 }
196 }
197
198 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 for command in commands {
208 output.push_str(&format!(
209 " \"{}\" [color=blue, style=filled, fillcolor=lightblue];\n",
210 command.name
211 ));
212 }
213
214 for type_name in self.resolved_types.keys() {
216 output.push_str(&format!(" \"{}\" [color=green];\n", type_name));
217 }
218
219 for command in commands {
221 for param in &command.parameters {
222 if self.resolved_types.contains_key(¶m.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 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}