Skip to main content

tauri_typegen/analysis/
mod.rs

1pub mod ast_cache;
2pub mod channel_parser;
3pub mod command_parser;
4pub mod dependency_graph;
5pub mod event_parser;
6pub mod serde_parser;
7pub mod struct_parser;
8pub mod type_resolver;
9pub mod validator_parser;
10
11use crate::models::{ChannelInfo, CommandInfo, EventInfo, StructInfo};
12use std::collections::{HashMap, HashSet};
13use std::path::{Path, PathBuf};
14
15use ast_cache::AstCache;
16use channel_parser::ChannelParser;
17use command_parser::CommandParser;
18use dependency_graph::TypeDependencyGraph;
19use event_parser::EventParser;
20use struct_parser::StructParser;
21use type_resolver::TypeResolver;
22
23/// Analyzer that orchestrates all analysis sub-modules
24pub struct CommandAnalyzer {
25    /// AST cache for parsed files
26    ast_cache: AstCache,
27    /// Command parser for extracting Tauri commands
28    command_parser: CommandParser,
29    /// Channel parser for extracting channel parameters
30    channel_parser: ChannelParser,
31    /// Event parser for extracting event emissions
32    event_parser: EventParser,
33    /// Struct parser for extracting type definitions
34    struct_parser: StructParser,
35    /// Type resolver for Rust to TypeScript type mappings
36    type_resolver: TypeResolver,
37    /// Dependency graph for type resolution
38    dependency_graph: TypeDependencyGraph,
39    /// Discovered struct definitions
40    discovered_structs: HashMap<String, StructInfo>,
41    /// Discovered event emissions
42    discovered_events: Vec<EventInfo>,
43}
44
45impl CommandAnalyzer {
46    pub fn new() -> Self {
47        Self {
48            ast_cache: AstCache::new(),
49            command_parser: CommandParser::new(),
50            channel_parser: ChannelParser::new(),
51            event_parser: EventParser::new(),
52            struct_parser: StructParser::new(),
53            type_resolver: TypeResolver::new(),
54            dependency_graph: TypeDependencyGraph::new(),
55            discovered_structs: HashMap::new(),
56            discovered_events: Vec::new(),
57        }
58    }
59
60    /// Add custom type mappings from configuration
61    pub fn add_type_mappings(&mut self, mappings: &HashMap<String, String>) {
62        for (rust_type, ts_type) in mappings {
63            self.type_resolver
64                .add_type_mapping(rust_type.clone(), ts_type.clone());
65        }
66    }
67
68    /// Analyze a complete project for Tauri commands and types
69    pub fn analyze_project(
70        &mut self,
71        project_path: &str,
72    ) -> Result<Vec<CommandInfo>, Box<dyn std::error::Error>> {
73        self.analyze_project_with_verbose(project_path, false)
74    }
75
76    /// Analyze a complete project for Tauri commands and types with verbose output
77    pub fn analyze_project_with_verbose(
78        &mut self,
79        project_path: &str,
80        verbose: bool,
81    ) -> Result<Vec<CommandInfo>, Box<dyn std::error::Error>> {
82        // Single pass: Parse all Rust files and cache ASTs
83        self.ast_cache
84            .parse_and_cache_all_files(project_path, verbose)?;
85
86        // Extract commands from cached ASTs
87        let mut file_paths: Vec<PathBuf> = self.ast_cache.keys().cloned().collect();
88        file_paths.sort_unstable();
89        let mut commands = Vec::new();
90        let mut type_names_to_discover = HashSet::new();
91
92        // Process each file - using functional style where possible
93        for file_path in file_paths {
94            if let Some(parsed_file) = self.ast_cache.get_cloned(&file_path) {
95                if verbose {
96                    println!("🔍 Analyzing file: {}", parsed_file.path.display());
97                }
98
99                // Extract commands from this file's AST
100                let mut file_commands = self.command_parser.extract_commands_from_ast(
101                    &parsed_file.ast,
102                    parsed_file.path.as_path(),
103                    &mut self.type_resolver,
104                )?;
105
106                // Extract channels for each command
107                for command in &mut file_commands {
108                    if let Some(func) = self.find_function_in_ast(&parsed_file.ast, &command.name) {
109                        let channels = self.channel_parser.extract_channels_from_command(
110                            func,
111                            &command.name,
112                            parsed_file.path.as_path(),
113                            &mut self.type_resolver,
114                        )?;
115
116                        // Collect type names from channel message types
117                        channels.iter().for_each(|ch| {
118                            self.extract_type_names(&ch.message_type, &mut type_names_to_discover);
119                        });
120
121                        command.channels = channels;
122                    }
123                }
124
125                // Extract events from this file's AST
126                let file_events = self.event_parser.extract_events_from_ast(
127                    &parsed_file.ast,
128                    parsed_file.path.as_path(),
129                    &mut self.type_resolver,
130                )?;
131
132                // Collect type names from command parameters and return types using functional style
133                file_commands.iter().for_each(|cmd| {
134                    cmd.parameters.iter().for_each(|param| {
135                        self.extract_type_names(&param.rust_type, &mut type_names_to_discover);
136                    });
137                    // Use the Rust return type (not TypeScript) to properly extract nested type names
138                    self.extract_type_names(&cmd.return_type, &mut type_names_to_discover);
139                });
140
141                // Collect type names from event payloads
142                file_events.iter().for_each(|event| {
143                    self.extract_type_names(&event.payload_type, &mut type_names_to_discover);
144                });
145
146                commands.extend(file_commands);
147                self.discovered_events.extend(file_events);
148
149                // Build type definition index from this file
150                self.index_type_definitions(&parsed_file.ast, parsed_file.path.as_path());
151            }
152        }
153
154        if verbose {
155            println!("🔍 Type names to discover: {:?}", type_names_to_discover);
156        }
157
158        // Lazy type resolution: Resolve types on demand using dependency graph
159        self.resolve_types_lazily(&type_names_to_discover)?;
160
161        if verbose {
162            println!(
163                "🏗️  Discovered {} structs total",
164                self.discovered_structs.len()
165            );
166            for (name, info) in &self.discovered_structs {
167                println!("  - {}: {} fields", name, info.fields.len());
168            }
169            println!(
170                "📡 Discovered {} events total",
171                self.discovered_events.len()
172            );
173            for event in &self.discovered_events {
174                println!("  - '{}': {}", event.event_name, event.payload_type);
175            }
176            let all_channels = self.get_all_discovered_channels(&commands);
177            println!("📞 Discovered {} channels total", all_channels.len());
178            for channel in &all_channels {
179                println!(
180                    "  - '{}' in {}: {}",
181                    channel.parameter_name, channel.command_name, channel.message_type
182                );
183            }
184        }
185
186        Ok(commands)
187    }
188
189    /// Analyze a single file for Tauri commands (backward compatibility for tests)
190    pub fn analyze_file(
191        &mut self,
192        file_path: &std::path::Path,
193    ) -> Result<Vec<CommandInfo>, Box<dyn std::error::Error>> {
194        let path_buf = file_path.to_path_buf();
195
196        // Parse and cache this single file - handle syntax errors gracefully
197        match self.ast_cache.parse_and_cache_file(&path_buf) {
198            Ok(_) => {
199                // Extract commands and events from the cached AST
200                if let Some(parsed_file) = self.ast_cache.get_cloned(&path_buf) {
201                    // Extract events
202                    let file_events = self.event_parser.extract_events_from_ast(
203                        &parsed_file.ast,
204                        path_buf.as_path(),
205                        &mut self.type_resolver,
206                    )?;
207                    self.discovered_events.extend(file_events);
208
209                    // Extract commands
210                    let mut commands = self.command_parser.extract_commands_from_ast(
211                        &parsed_file.ast,
212                        path_buf.as_path(),
213                        &mut self.type_resolver,
214                    )?;
215
216                    // Extract channels for each command
217                    for command in &mut commands {
218                        if let Some(func) =
219                            self.find_function_in_ast(&parsed_file.ast, &command.name)
220                        {
221                            let channels = self.channel_parser.extract_channels_from_command(
222                                func,
223                                &command.name,
224                                path_buf.as_path(),
225                                &mut self.type_resolver,
226                            )?;
227
228                            command.channels = channels;
229                        }
230                    }
231
232                    Ok(commands)
233                } else {
234                    Ok(vec![])
235                }
236            }
237            Err(_) => {
238                // Return empty vector for files with syntax errors (backward compatibility)
239                Ok(vec![])
240            }
241        }
242    }
243
244    /// Build an index of type definitions from an AST
245    fn index_type_definitions(&mut self, ast: &syn::File, file_path: &Path) {
246        self.index_items(&ast.items, file_path);
247    }
248
249    /// Recursively index items for type definitions
250    fn index_items(&mut self, items: &[syn::Item], file_path: &Path) {
251        for item in items {
252            match item {
253                syn::Item::Struct(item_struct) => {
254                    if self.struct_parser.should_include_struct(item_struct) {
255                        let struct_name = item_struct.ident.to_string();
256                        self.dependency_graph
257                            .add_type_definition(struct_name, file_path.to_path_buf());
258                    }
259                }
260                syn::Item::Enum(item_enum) => {
261                    if self.struct_parser.should_include_enum(item_enum) {
262                        let enum_name = item_enum.ident.to_string();
263                        self.dependency_graph
264                            .add_type_definition(enum_name, file_path.to_path_buf());
265                    }
266                }
267                syn::Item::Mod(item_mod) => {
268                    if let Some((_, items)) = &item_mod.content {
269                        self.index_items(items, file_path);
270                    }
271                }
272                _ => {}
273            }
274        }
275    }
276
277    /// Lazily resolve types using the dependency graph
278    fn resolve_types_lazily(
279        &mut self,
280        initial_types: &HashSet<String>,
281    ) -> Result<(), Box<dyn std::error::Error>> {
282        let mut types_to_resolve: Vec<String> = initial_types.iter().cloned().collect();
283        let mut resolved_types = HashSet::new();
284
285        while let Some(type_name) = types_to_resolve.pop() {
286            // Skip if already resolved
287            if resolved_types.contains(&type_name)
288                || self.discovered_structs.contains_key(&type_name)
289            {
290                continue;
291            }
292
293            // Try to resolve this type
294            if let Some(file_path) = self
295                .dependency_graph
296                .get_type_definition_path(&type_name)
297                .cloned()
298            {
299                if let Some(parsed_file) = self.ast_cache.get_cloned(&file_path) {
300                    // Find and parse the specific type from the cached AST
301                    if let Some(struct_info) = self.extract_type_from_ast(
302                        &parsed_file.ast,
303                        &type_name,
304                        file_path.as_path(),
305                    ) {
306                        // Collect dependencies of this type
307                        let mut type_dependencies = HashSet::new();
308                        for field in &struct_info.fields {
309                            self.extract_type_names(&field.rust_type, &mut type_dependencies);
310                        }
311
312                        // Collect dependencies from enum variants
313                        if let Some(variants) = &struct_info.enum_variants {
314                            for variant in variants {
315                                match &variant.kind {
316                                    crate::models::EnumVariantKind::Unit => {}
317                                    crate::models::EnumVariantKind::Tuple(types) => {
318                                        for type_struct in types {
319                                            let mut variant_types = HashSet::new();
320                                            crate::generators::TypeCollector::collect_referenced_types_from_structure(
321                                                type_struct,
322                                                &mut variant_types,
323                                            );
324                                            type_dependencies.extend(variant_types);
325                                        }
326                                    }
327                                    crate::models::EnumVariantKind::Struct(fields) => {
328                                        for field in fields {
329                                            self.extract_type_names(
330                                                &field.rust_type,
331                                                &mut type_dependencies,
332                                            );
333                                        }
334                                    }
335                                }
336                            }
337                        }
338
339                        // Add dependencies to the resolution queue
340                        for dep_type in &type_dependencies {
341                            if !resolved_types.contains(dep_type)
342                                && !self.discovered_structs.contains_key(dep_type)
343                                && self.dependency_graph.has_type_definition(dep_type)
344                            {
345                                types_to_resolve.push(dep_type.clone());
346                            }
347                        }
348
349                        // Store the resolved type
350                        self.dependency_graph
351                            .add_dependencies(type_name.clone(), type_dependencies.clone());
352                        self.dependency_graph
353                            .add_resolved_type(type_name.clone(), struct_info.clone());
354                        self.discovered_structs
355                            .insert(type_name.clone(), struct_info);
356                        resolved_types.insert(type_name);
357                    }
358                }
359            }
360        }
361
362        Ok(())
363    }
364
365    /// Extract a specific type from a cached AST
366    fn extract_type_from_ast(
367        &mut self,
368        ast: &syn::File,
369        type_name: &str,
370        file_path: &Path,
371    ) -> Option<StructInfo> {
372        self.find_type_in_items(&ast.items, type_name, file_path)
373    }
374
375    /// Recursively find a type in a list of items
376    fn find_type_in_items(
377        &mut self,
378        items: &[syn::Item],
379        type_name: &str,
380        file_path: &Path,
381    ) -> Option<StructInfo> {
382        for item in items {
383            match item {
384                syn::Item::Struct(item_struct) => {
385                    if item_struct.ident == type_name
386                        && self.struct_parser.should_include_struct(item_struct)
387                    {
388                        return self.struct_parser.parse_struct(
389                            item_struct,
390                            file_path,
391                            &mut self.type_resolver,
392                        );
393                    }
394                }
395                syn::Item::Enum(item_enum) => {
396                    if item_enum.ident == type_name
397                        && self.struct_parser.should_include_enum(item_enum)
398                    {
399                        return self.struct_parser.parse_enum(
400                            item_enum,
401                            file_path,
402                            &mut self.type_resolver,
403                        );
404                    }
405                }
406                syn::Item::Mod(item_mod) => {
407                    if let Some((_, items)) = &item_mod.content {
408                        if let Some(info) = self.find_type_in_items(items, type_name, file_path) {
409                            return Some(info);
410                        }
411                    }
412                }
413                _ => {}
414            }
415        }
416        None
417    }
418
419    /// Extract type names from a Rust type string
420    pub fn extract_type_names(&self, rust_type: &str, type_names: &mut HashSet<String>) {
421        self.extract_type_names_recursive(rust_type, type_names);
422    }
423
424    /// Recursively extract type names from complex types
425    fn extract_type_names_recursive(&self, rust_type: &str, type_names: &mut HashSet<String>) {
426        let rust_type = rust_type.trim();
427
428        // Handle references first
429        if rust_type.starts_with('&') {
430            let without_ref = rust_type.trim_start_matches('&');
431            self.extract_type_names_recursive(without_ref, type_names);
432            return;
433        }
434
435        // Strip module prefixes like std::, ::std::, ::core::, etc. for generic type detection
436        // but keep the original for custom type name detection
437        let stripped = Self::strip_module_prefix(rust_type);
438
439        // Handle Result<T, E> - extract both T and E
440        if stripped.starts_with("Result<") {
441            if let Some(inner) = stripped
442                .strip_prefix("Result<")
443                .and_then(|s| s.strip_suffix(">"))
444            {
445                if let Some(comma_pos) = inner.find(',') {
446                    let ok_type = inner[..comma_pos].trim();
447                    let err_type = inner[comma_pos + 1..].trim();
448                    self.extract_type_names_recursive(ok_type, type_names);
449                    self.extract_type_names_recursive(err_type, type_names);
450                }
451            }
452            return;
453        }
454
455        // Handle Option<T> - extract T (handles both Option<T> and ::core::option::Option<T>)
456        if stripped.starts_with("Option<") {
457            if let Some(inner) = stripped
458                .strip_prefix("Option<")
459                .and_then(|s| s.strip_suffix(">"))
460            {
461                self.extract_type_names_recursive(inner, type_names);
462            }
463            return;
464        }
465
466        // Handle Vec<T> - extract T (handles both Vec<T> and ::std::vec::Vec<T>)
467        if stripped.starts_with("Vec<") {
468            if let Some(inner) = stripped
469                .strip_prefix("Vec<")
470                .and_then(|s| s.strip_suffix(">"))
471            {
472                self.extract_type_names_recursive(inner, type_names);
473            }
474            return;
475        }
476
477        // Handle HashMap<K, V> and BTreeMap<K, V> - extract K and V
478        if stripped.starts_with("HashMap<") || stripped.starts_with("BTreeMap<") {
479            let prefix = if stripped.starts_with("HashMap<") {
480                "HashMap<"
481            } else {
482                "BTreeMap<"
483            };
484            if let Some(inner) = stripped
485                .strip_prefix(prefix)
486                .and_then(|s| s.strip_suffix(">"))
487            {
488                if let Some(comma_pos) = inner.find(',') {
489                    let key_type = inner[..comma_pos].trim();
490                    let value_type = inner[comma_pos + 1..].trim();
491                    self.extract_type_names_recursive(key_type, type_names);
492                    self.extract_type_names_recursive(value_type, type_names);
493                }
494            }
495            return;
496        }
497
498        // Handle HashSet<T> and BTreeSet<T> - extract T
499        if stripped.starts_with("HashSet<") || stripped.starts_with("BTreeSet<") {
500            let prefix = if stripped.starts_with("HashSet<") {
501                "HashSet<"
502            } else {
503                "BTreeSet<"
504            };
505            if let Some(inner) = stripped
506                .strip_prefix(prefix)
507                .and_then(|s| s.strip_suffix(">"))
508            {
509                self.extract_type_names_recursive(inner, type_names);
510            }
511            return;
512        }
513
514        // Handle tuple types like (T, U, V)
515        if rust_type.starts_with('(') && rust_type.ends_with(')') && rust_type != "()" {
516            let inner = &rust_type[1..rust_type.len() - 1];
517            for part in inner.split(',') {
518                self.extract_type_names_recursive(part.trim(), type_names);
519            }
520            return;
521        }
522
523        // Check if this is a custom type name
524        if !rust_type.is_empty()
525            && !self.type_resolver.get_type_set().contains(rust_type)
526            && !rust_type.starts_with(char::is_lowercase) // Skip built-in types
527            && rust_type.chars().next().is_some_and(char::is_alphabetic)
528            && !rust_type.contains('<')
529        // Skip generic type names with parameters
530        {
531            // Extract just the type name, stripping module prefix if present
532            let type_name = Self::extract_simple_type_name(rust_type);
533            type_names.insert(type_name);
534        }
535    }
536
537    /// Strip module prefixes like std::, ::std::, ::core::, crate::, etc.
538    /// Used for pattern matching on generic types
539    fn strip_module_prefix(rust_type: &str) -> &str {
540        // Find the last :: to separate module path from type name
541        if let Some(last_double_colon) = rust_type.rfind("::") {
542            // Only strip if what follows contains < (it's a generic type)
543            let after_colon = &rust_type[last_double_colon + 2..];
544            if after_colon.contains('<') {
545                return after_colon;
546            }
547        }
548        rust_type
549    }
550
551    /// Extract just the type name from a potentially module-qualified name
552    /// E.g., "::my_module::MyType" -> "MyType"
553    fn extract_simple_type_name(rust_type: &str) -> String {
554        // Take everything after the last ::, or the whole thing if no ::
555        if let Some(last_double_colon) = rust_type.rfind("::") {
556            rust_type[last_double_colon + 2..].to_string()
557        } else {
558            rust_type.to_string()
559        }
560    }
561
562    /// Get discovered structs
563    pub fn get_discovered_structs(&self) -> &HashMap<String, StructInfo> {
564        &self.discovered_structs
565    }
566
567    /// Get discovered events
568    pub fn get_discovered_events(&self) -> &[EventInfo] {
569        &self.discovered_events
570    }
571
572    /// Get reference to the type resolver
573    pub fn get_type_resolver(&self) -> std::cell::RefCell<&TypeResolver> {
574        std::cell::RefCell::new(&self.type_resolver)
575    }
576
577    /// Get all discovered channels from all commands
578    pub fn get_all_discovered_channels(&self, commands: &[CommandInfo]) -> Vec<ChannelInfo> {
579        commands
580            .iter()
581            .flat_map(|cmd| cmd.channels.clone())
582            .collect()
583    }
584
585    /// Find a function by name in an AST (recursive)
586    fn find_function_in_ast<'a>(
587        &self,
588        ast: &'a syn::File,
589        function_name: &str,
590    ) -> Option<&'a syn::ItemFn> {
591        self.find_function_in_items(&ast.items, function_name)
592    }
593
594    /// Recursively find a function in a list of items
595    fn find_function_in_items<'a>(
596        &self,
597        items: &'a [syn::Item],
598        function_name: &str,
599    ) -> Option<&'a syn::ItemFn> {
600        for item in items {
601            match item {
602                syn::Item::Fn(func) => {
603                    if func.sig.ident == function_name {
604                        return Some(func);
605                    }
606                }
607                syn::Item::Mod(item_mod) => {
608                    if let Some((_, items)) = &item_mod.content {
609                        if let Some(func) = self.find_function_in_items(items, function_name) {
610                            return Some(func);
611                        }
612                    }
613                }
614                _ => {}
615            }
616        }
617        None
618    }
619
620    /// Get the dependency graph for visualization
621    pub fn get_dependency_graph(&self) -> &TypeDependencyGraph {
622        &self.dependency_graph
623    }
624
625    /// Sort types topologically to ensure dependencies are declared before being used
626    pub fn topological_sort_types(&self, types: &HashSet<String>) -> Vec<String> {
627        self.dependency_graph.topological_sort_types(types)
628    }
629
630    /// Generate a text-based visualization of the dependency graph
631    pub fn visualize_dependencies(&self, commands: &[CommandInfo]) -> String {
632        self.dependency_graph.visualize_dependencies(commands)
633    }
634
635    /// Generate a DOT graph visualization of the dependency graph
636    pub fn generate_dot_graph(&self, commands: &[CommandInfo]) -> String {
637        self.dependency_graph.generate_dot_graph(commands)
638    }
639}
640
641impl Default for CommandAnalyzer {
642    fn default() -> Self {
643        Self::new()
644    }
645}
646
647#[cfg(test)]
648mod tests {
649    use super::*;
650    use std::collections::HashSet;
651
652    fn analyzer() -> CommandAnalyzer {
653        CommandAnalyzer::new()
654    }
655
656    mod initialization {
657        use super::*;
658
659        #[test]
660        fn test_new_creates_analyzer() {
661            let analyzer = CommandAnalyzer::new();
662            assert!(analyzer.get_discovered_structs().is_empty());
663            assert!(analyzer.get_discovered_events().is_empty());
664        }
665
666        #[test]
667        fn test_default_creates_analyzer() {
668            let analyzer = CommandAnalyzer::default();
669            assert!(analyzer.get_discovered_structs().is_empty());
670            assert!(analyzer.get_discovered_events().is_empty());
671        }
672    }
673
674    mod type_name_extraction {
675        use super::*;
676
677        #[test]
678        fn test_extract_simple_type() {
679            let analyzer = analyzer();
680            let mut types = HashSet::new();
681            analyzer.extract_type_names("User", &mut types);
682            assert_eq!(types.len(), 1);
683            assert!(types.contains("User"));
684        }
685
686        #[test]
687        fn test_extract_option_type() {
688            let analyzer = analyzer();
689            let mut types = HashSet::new();
690            analyzer.extract_type_names("Option<User>", &mut types);
691            assert_eq!(types.len(), 1);
692            assert!(types.contains("User"));
693        }
694
695        #[test]
696        fn test_extract_vec_type() {
697            let analyzer = analyzer();
698            let mut types = HashSet::new();
699            analyzer.extract_type_names("Vec<Product>", &mut types);
700            assert_eq!(types.len(), 1);
701            assert!(types.contains("Product"));
702        }
703
704        #[test]
705        fn test_extract_result_type() {
706            let analyzer = analyzer();
707            let mut types = HashSet::new();
708            analyzer.extract_type_names("Result<User, AppError>", &mut types);
709            assert_eq!(types.len(), 2);
710            assert!(types.contains("User"));
711            assert!(types.contains("AppError"));
712        }
713
714        #[test]
715        fn test_extract_hashmap_type() {
716            let analyzer = analyzer();
717            let mut types = HashSet::new();
718            analyzer.extract_type_names("HashMap<String, User>", &mut types);
719            // String is a primitive, should only extract User
720            assert_eq!(types.len(), 1);
721            assert!(types.contains("User"));
722        }
723
724        #[test]
725        fn test_extract_btreemap_type() {
726            let analyzer = analyzer();
727            let mut types = HashSet::new();
728            analyzer.extract_type_names("BTreeMap<UserId, Profile>", &mut types);
729            assert_eq!(types.len(), 2);
730            assert!(types.contains("UserId"));
731            assert!(types.contains("Profile"));
732        }
733
734        #[test]
735        fn test_extract_hashset_type() {
736            let analyzer = analyzer();
737            let mut types = HashSet::new();
738            analyzer.extract_type_names("HashSet<User>", &mut types);
739            assert_eq!(types.len(), 1);
740            assert!(types.contains("User"));
741        }
742
743        #[test]
744        fn test_extract_btreeset_type() {
745            let analyzer = analyzer();
746            let mut types = HashSet::new();
747            analyzer.extract_type_names("BTreeSet<Tag>", &mut types);
748            assert_eq!(types.len(), 1);
749            assert!(types.contains("Tag"));
750        }
751
752        #[test]
753        fn test_extract_tuple_type() {
754            let analyzer = analyzer();
755            let mut types = HashSet::new();
756            analyzer.extract_type_names("(User, Product, Order)", &mut types);
757            assert_eq!(types.len(), 3);
758            assert!(types.contains("User"));
759            assert!(types.contains("Product"));
760            assert!(types.contains("Order"));
761        }
762
763        #[test]
764        fn test_extract_reference_type() {
765            let analyzer = analyzer();
766            let mut types = HashSet::new();
767            analyzer.extract_type_names("&User", &mut types);
768            assert_eq!(types.len(), 1);
769            assert!(types.contains("User"));
770        }
771
772        #[test]
773        fn test_extract_nested_types() {
774            let analyzer = analyzer();
775            let mut types = HashSet::new();
776            analyzer.extract_type_names("Vec<Option<User>>", &mut types);
777            assert_eq!(types.len(), 1);
778            assert!(types.contains("User"));
779        }
780
781        #[test]
782        fn test_extract_deeply_nested_types() {
783            let analyzer = analyzer();
784            let mut types = HashSet::new();
785            analyzer.extract_type_names("HashMap<String, Vec<Option<Product>>>", &mut types);
786            assert_eq!(types.len(), 1);
787            assert!(types.contains("Product"));
788        }
789
790        #[test]
791        fn test_skips_primitive_types() {
792            let analyzer = analyzer();
793            let mut types = HashSet::new();
794            analyzer.extract_type_names("String", &mut types);
795            assert_eq!(types.len(), 0);
796        }
797
798        #[test]
799        fn test_skips_built_in_types() {
800            let analyzer = analyzer();
801            let mut types = HashSet::new();
802            analyzer.extract_type_names("i32", &mut types);
803            assert_eq!(types.len(), 0);
804        }
805
806        #[test]
807        fn test_skips_empty_type() {
808            let analyzer = analyzer();
809            let mut types = HashSet::new();
810            analyzer.extract_type_names("", &mut types);
811            assert_eq!(types.len(), 0);
812        }
813
814        #[test]
815        fn test_skips_unit_type() {
816            let analyzer = analyzer();
817            let mut types = HashSet::new();
818            analyzer.extract_type_names("()", &mut types);
819            assert_eq!(types.len(), 0);
820        }
821
822        #[test]
823        fn test_multiple_calls_accumulate() {
824            let analyzer = analyzer();
825            let mut types = HashSet::new();
826            analyzer.extract_type_names("User", &mut types);
827            analyzer.extract_type_names("Product", &mut types);
828            assert_eq!(types.len(), 2);
829            assert!(types.contains("User"));
830            assert!(types.contains("Product"));
831        }
832
833        #[test]
834        fn test_duplicate_types_deduped() {
835            let analyzer = analyzer();
836            let mut types = HashSet::new();
837            analyzer.extract_type_names("User", &mut types);
838            analyzer.extract_type_names("User", &mut types);
839            assert_eq!(types.len(), 1);
840        }
841    }
842
843    mod getters {
844        use super::*;
845
846        #[test]
847        fn test_get_discovered_structs_empty() {
848            let analyzer = analyzer();
849            let structs = analyzer.get_discovered_structs();
850            assert!(structs.is_empty());
851        }
852
853        #[test]
854        fn test_get_discovered_events_empty() {
855            let analyzer = analyzer();
856            let events = analyzer.get_discovered_events();
857            assert!(events.is_empty());
858        }
859
860        #[test]
861        fn test_get_type_resolver() {
862            let analyzer = analyzer();
863            let resolver = analyzer.get_type_resolver();
864            // Just verify it returns a RefCell
865            assert!(!resolver.borrow().get_type_set().is_empty());
866        }
867
868        #[test]
869        fn test_get_dependency_graph() {
870            let analyzer = analyzer();
871            let graph = analyzer.get_dependency_graph();
872            // Verify graph exists (check resolved types)
873            assert!(graph.get_resolved_types().is_empty());
874        }
875
876        #[test]
877        fn test_get_all_discovered_channels_empty() {
878            let analyzer = analyzer();
879            let commands = vec![];
880            let channels = analyzer.get_all_discovered_channels(&commands);
881            assert!(channels.is_empty());
882        }
883
884        #[test]
885        fn test_get_all_discovered_channels_with_commands() {
886            let analyzer = analyzer();
887            let command = CommandInfo::new_for_test(
888                "test_cmd",
889                "test.rs",
890                1,
891                vec![],
892                "void",
893                false,
894                vec![
895                    ChannelInfo::new_for_test("ch1", "Message1", "test_cmd", "test.rs", 10),
896                    ChannelInfo::new_for_test("ch2", "Message2", "test_cmd", "test.rs", 20),
897                ],
898            );
899
900            let commands = vec![command];
901            let channels = analyzer.get_all_discovered_channels(&commands);
902            assert_eq!(channels.len(), 2);
903        }
904    }
905
906    mod topological_sort {
907        use super::*;
908
909        #[test]
910        fn test_topological_sort_empty() {
911            let analyzer = analyzer();
912            let types = HashSet::new();
913            let sorted = analyzer.topological_sort_types(&types);
914            assert!(sorted.is_empty());
915        }
916
917        #[test]
918        fn test_topological_sort_single_type() {
919            let mut analyzer = analyzer();
920            let path = PathBuf::from("test.rs");
921            analyzer
922                .dependency_graph
923                .add_type_definition("User".to_string(), path);
924
925            let mut types = HashSet::new();
926            types.insert("User".to_string());
927
928            let sorted = analyzer.topological_sort_types(&types);
929            assert_eq!(sorted.len(), 1);
930            assert_eq!(sorted[0], "User");
931        }
932    }
933
934    mod ast_helpers {
935        use super::*;
936        use syn::{parse_quote, File as SynFile};
937
938        #[test]
939        fn test_find_function_in_ast() {
940            let analyzer = analyzer();
941            let ast: SynFile = parse_quote! {
942                #[tauri::command]
943                fn my_command() -> String {
944                    "test".to_string()
945                }
946
947                fn other_function() {}
948            };
949
950            let result = analyzer.find_function_in_ast(&ast, "my_command");
951            assert!(result.is_some());
952            assert_eq!(result.unwrap().sig.ident, "my_command");
953        }
954
955        #[test]
956        fn test_find_function_in_ast_not_found() {
957            let analyzer = analyzer();
958            let ast: SynFile = parse_quote! {
959                fn my_command() {}
960            };
961
962            let result = analyzer.find_function_in_ast(&ast, "non_existent");
963            assert!(result.is_none());
964        }
965
966        #[test]
967        fn test_find_function_in_ast_empty() {
968            let analyzer = analyzer();
969            let ast: SynFile = parse_quote! {};
970
971            let result = analyzer.find_function_in_ast(&ast, "any_function");
972            assert!(result.is_none());
973        }
974    }
975
976    mod index_type_definitions {
977        use super::*;
978        use syn::{parse_quote, File as SynFile};
979
980        #[test]
981        fn test_index_struct() {
982            let mut analyzer = analyzer();
983            let ast: SynFile = parse_quote! {
984                #[derive(Serialize)]
985                pub struct User {
986                    name: String,
987                }
988            };
989            let path = Path::new("test.rs");
990
991            analyzer.index_type_definitions(&ast, path);
992
993            assert!(analyzer.dependency_graph.has_type_definition("User"));
994        }
995
996        #[test]
997        fn test_index_enum() {
998            let mut analyzer = analyzer();
999            let ast: SynFile = parse_quote! {
1000                #[derive(Serialize)]
1001                pub enum Status {
1002                    Active,
1003                    Inactive,
1004                }
1005            };
1006            let path = Path::new("test.rs");
1007
1008            analyzer.index_type_definitions(&ast, path);
1009
1010            assert!(analyzer.dependency_graph.has_type_definition("Status"));
1011        }
1012
1013        #[test]
1014        fn test_skips_non_serde_types() {
1015            let mut analyzer = analyzer();
1016            let ast: SynFile = parse_quote! {
1017                #[derive(Debug, Clone)]
1018                pub struct User {
1019                    name: String,
1020                }
1021            };
1022            let path = Path::new("test.rs");
1023
1024            analyzer.index_type_definitions(&ast, path);
1025
1026            assert!(!analyzer.dependency_graph.has_type_definition("User"));
1027        }
1028    }
1029
1030    mod extract_type_from_ast {
1031        use super::*;
1032        use syn::{parse_quote, File as SynFile};
1033
1034        #[test]
1035        fn test_extract_struct_from_ast() {
1036            let mut analyzer = analyzer();
1037            let ast: SynFile = parse_quote! {
1038                #[derive(Serialize)]
1039                pub struct User {
1040                    pub name: String,
1041                }
1042            };
1043            let path = Path::new("test.rs");
1044
1045            let result = analyzer.extract_type_from_ast(&ast, "User", path);
1046            assert!(result.is_some());
1047            let struct_info = result.unwrap();
1048            assert_eq!(struct_info.name, "User");
1049            assert_eq!(struct_info.fields.len(), 1);
1050        }
1051
1052        #[test]
1053        fn test_extract_enum_from_ast() {
1054            let mut analyzer = analyzer();
1055            let ast: SynFile = parse_quote! {
1056                #[derive(Serialize)]
1057                pub enum Status {
1058                    Active,
1059                    Inactive,
1060                }
1061            };
1062            let path = Path::new("test.rs");
1063
1064            let result = analyzer.extract_type_from_ast(&ast, "Status", path);
1065            assert!(result.is_some());
1066            let enum_info = result.unwrap();
1067            assert_eq!(enum_info.name, "Status");
1068            assert!(enum_info.is_enum);
1069        }
1070
1071        #[test]
1072        fn test_extract_type_not_found() {
1073            let mut analyzer = analyzer();
1074            let ast: SynFile = parse_quote! {
1075                #[derive(Serialize)]
1076                pub struct User {
1077                    name: String,
1078                }
1079            };
1080            let path = Path::new("test.rs");
1081
1082            let result = analyzer.extract_type_from_ast(&ast, "Product", path);
1083            assert!(result.is_none());
1084        }
1085
1086        #[test]
1087        fn test_extract_type_without_serde() {
1088            let mut analyzer = analyzer();
1089            let ast: SynFile = parse_quote! {
1090                #[derive(Debug)]
1091                pub struct User {
1092                    name: String,
1093                }
1094            };
1095            let path = Path::new("test.rs");
1096
1097            let result = analyzer.extract_type_from_ast(&ast, "User", path);
1098            assert!(result.is_none());
1099        }
1100    }
1101
1102    mod visualization {
1103        use super::*;
1104
1105        #[test]
1106        fn test_visualize_dependencies() {
1107            let analyzer = analyzer();
1108            let commands = vec![];
1109            let viz = analyzer.visualize_dependencies(&commands);
1110            // Just verify it returns a string
1111            assert!(viz.contains("Dependency Graph"));
1112        }
1113
1114        #[test]
1115        fn test_generate_dot_graph() {
1116            let analyzer = analyzer();
1117            let commands = vec![];
1118            let dot = analyzer.generate_dot_graph(&commands);
1119            // Verify basic DOT format
1120            assert!(dot.contains("digraph"));
1121        }
1122    }
1123}