Skip to main content

tauri_typegen/analysis/
event_parser.rs

1use crate::analysis::type_resolver::TypeResolver;
2use crate::models::EventInfo;
3use std::collections::HashMap;
4use std::path::Path;
5use syn::{Expr, ExprMethodCall, File as SynFile, FnArg, Lit, Pat, Type};
6
7/// Parser for Tauri event emissions
8#[derive(Debug)]
9pub struct EventParser;
10
11/// Simple symbol table to track variable names to their types
12type SymbolTable = HashMap<String, String>;
13
14impl EventParser {
15    pub fn new() -> Self {
16        Self
17    }
18
19    /// Extract event emissions from a cached AST (including nested modules)
20    pub fn extract_events_from_ast(
21        &self,
22        ast: &SynFile,
23        file_path: &Path,
24        type_resolver: &mut TypeResolver,
25    ) -> Result<Vec<EventInfo>, Box<dyn std::error::Error>> {
26        let mut events = Vec::new();
27        self.extract_events_from_items(&ast.items, file_path, type_resolver, &mut events);
28        Ok(events)
29    }
30
31    /// Recursively search through items for events
32    fn extract_events_from_items(
33        &self,
34        items: &[syn::Item],
35        file_path: &Path,
36        type_resolver: &mut TypeResolver,
37        events: &mut Vec<EventInfo>,
38    ) {
39        for item in items {
40            match item {
41                syn::Item::Fn(func) => {
42                    // Build symbol table from function parameters
43                    let mut symbols = SymbolTable::new();
44                    self.extract_param_types(&func.sig.inputs, &mut symbols);
45
46                    // Search within function bodies with symbol context
47                    self.extract_events_from_block(
48                        &func.block.stmts,
49                        file_path,
50                        type_resolver,
51                        events,
52                        &mut symbols,
53                    );
54                }
55                syn::Item::Mod(item_mod) => {
56                    if let Some((_, items)) = &item_mod.content {
57                        self.extract_events_from_items(items, file_path, type_resolver, events);
58                    }
59                }
60                _ => {}
61            }
62        }
63    }
64
65    /// Extract parameter types from function signature into symbol table
66    fn extract_param_types(
67        &self,
68        inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
69        symbols: &mut SymbolTable,
70    ) {
71        for arg in inputs {
72            if let FnArg::Typed(pat_type) = arg {
73                if let Pat::Ident(pat_ident) = &*pat_type.pat {
74                    let param_name = pat_ident.ident.to_string();
75                    let param_type = self.extract_type_name(&pat_type.ty);
76                    symbols.insert(param_name, param_type);
77                }
78            }
79        }
80    }
81
82    /// Extract the type name from a Type, handling references and generic wrappers
83    fn extract_type_name(&self, ty: &Type) -> String {
84        match ty {
85            Type::Reference(type_ref) => {
86                // Handle &T and &mut T - extract the inner type
87                self.extract_type_name(&type_ref.elem)
88            }
89            Type::Path(type_path) => {
90                // Get the last segment of the path (the actual type name)
91                if let Some(segment) = type_path.path.segments.last() {
92                    segment.ident.to_string()
93                } else {
94                    "unknown".to_string()
95                }
96            }
97            _ => "unknown".to_string(),
98        }
99    }
100
101    /// Recursively search through statements for emit calls
102    fn extract_events_from_block(
103        &self,
104        stmts: &[syn::Stmt],
105        file_path: &Path,
106        type_resolver: &mut TypeResolver,
107        events: &mut Vec<EventInfo>,
108        symbols: &mut SymbolTable,
109    ) {
110        for stmt in stmts {
111            self.extract_events_from_stmt(stmt, file_path, type_resolver, events, symbols);
112        }
113    }
114
115    /// Extract events from a single statement
116    fn extract_events_from_stmt(
117        &self,
118        stmt: &syn::Stmt,
119        file_path: &Path,
120        type_resolver: &mut TypeResolver,
121        events: &mut Vec<EventInfo>,
122        symbols: &mut SymbolTable,
123    ) {
124        match stmt {
125            syn::Stmt::Expr(expr, _) => {
126                self.extract_events_from_expr(expr, file_path, type_resolver, events, symbols);
127            }
128            syn::Stmt::Local(local) => {
129                // Track let bindings with explicit types
130                self.extract_local_binding(local, symbols);
131
132                if let Some(init) = &local.init {
133                    self.extract_events_from_expr(
134                        &init.expr,
135                        file_path,
136                        type_resolver,
137                        events,
138                        symbols,
139                    );
140                }
141            }
142            _ => {}
143        }
144    }
145
146    /// Extract variable binding from let statement
147    fn extract_local_binding(&self, local: &syn::Local, symbols: &mut SymbolTable) {
148        // Handle let var: Type = ...
149        if let Pat::Ident(pat_ident) = &local.pat {
150            let var_name = pat_ident.ident.to_string();
151
152            // If there's an explicit type annotation, use it
153            if let Some(local_init) = &local.init {
154                // Try to infer type from the initialization expression
155                let inferred_type = self.infer_type_from_init(&local_init.expr, symbols);
156                if inferred_type != "unknown" {
157                    symbols.insert(var_name, inferred_type);
158                }
159            }
160        }
161
162        // Handle let var: Type (with type annotation via local.ty if it were available)
163        // syn's Local doesn't have direct type annotation in newer versions,
164        // but we can handle patterns with type annotations
165        if let Pat::Type(pat_type) = &local.pat {
166            if let Pat::Ident(pat_ident) = &*pat_type.pat {
167                let var_name = pat_ident.ident.to_string();
168                let var_type = self.extract_type_name(&pat_type.ty);
169                symbols.insert(var_name, var_type);
170            }
171        }
172    }
173
174    /// Try to infer type from initialization expression
175    fn infer_type_from_init(&self, expr: &Expr, symbols: &SymbolTable) -> String {
176        match expr {
177            Expr::Struct(expr_struct) => {
178                // Struct construction: Type { ... } or Enum::Variant { ... }
179                let segments = &expr_struct.path.segments;
180                if segments.len() >= 2 {
181                    // It's likely an Enum variant: MyEnum::MyVariant { ... }
182                    return segments[0].ident.to_string();
183                } else if let Some(segment) = segments.last() {
184                    return segment.ident.to_string();
185                }
186            }
187            Expr::Call(call) => {
188                // Function call like Type::new() or Type::default()
189                if let Expr::Path(path) = &*call.func {
190                    // Check for Type::method() pattern
191                    if path.path.segments.len() >= 2 {
192                        return path.path.segments[0].ident.to_string();
193                    }
194                }
195            }
196            Expr::Path(path) => {
197                // Variable reference - look up in symbol table
198                if let Some(ident) = path.path.get_ident() {
199                    let name = ident.to_string();
200                    if let Some(typ) = symbols.get(&name) {
201                        return typ.clone();
202                    }
203                }
204            }
205            Expr::Reference(expr_ref) => {
206                // &expr - recurse into inner expression
207                return self.infer_type_from_init(&expr_ref.expr, symbols);
208            }
209            _ => {}
210        }
211        "unknown".to_string()
212    }
213
214    /// Extract events from an expression
215    fn extract_events_from_expr(
216        &self,
217        expr: &Expr,
218        file_path: &Path,
219        type_resolver: &mut TypeResolver,
220        events: &mut Vec<EventInfo>,
221        symbols: &mut SymbolTable,
222    ) {
223        match expr {
224            Expr::MethodCall(method_call) => {
225                self.handle_method_call(method_call, file_path, type_resolver, events, symbols);
226            }
227            Expr::Block(block) => {
228                self.extract_events_from_block(
229                    &block.block.stmts,
230                    file_path,
231                    type_resolver,
232                    events,
233                    symbols,
234                );
235            }
236            Expr::If(expr_if) => {
237                self.extract_events_from_block(
238                    &expr_if.then_branch.stmts,
239                    file_path,
240                    type_resolver,
241                    events,
242                    symbols,
243                );
244                if let Some((_, else_branch)) = &expr_if.else_branch {
245                    self.extract_events_from_expr(
246                        else_branch,
247                        file_path,
248                        type_resolver,
249                        events,
250                        symbols,
251                    );
252                }
253            }
254            Expr::Match(expr_match) => {
255                for arm in &expr_match.arms {
256                    self.extract_events_from_expr(
257                        &arm.body,
258                        file_path,
259                        type_resolver,
260                        events,
261                        symbols,
262                    );
263                }
264            }
265            Expr::Loop(expr_loop) => {
266                self.extract_events_from_block(
267                    &expr_loop.body.stmts,
268                    file_path,
269                    type_resolver,
270                    events,
271                    symbols,
272                );
273            }
274            Expr::While(expr_while) => {
275                self.extract_events_from_block(
276                    &expr_while.body.stmts,
277                    file_path,
278                    type_resolver,
279                    events,
280                    symbols,
281                );
282            }
283            Expr::ForLoop(expr_for) => {
284                self.extract_events_from_block(
285                    &expr_for.body.stmts,
286                    file_path,
287                    type_resolver,
288                    events,
289                    symbols,
290                );
291            }
292            Expr::Await(expr_await) => {
293                self.extract_events_from_expr(
294                    &expr_await.base,
295                    file_path,
296                    type_resolver,
297                    events,
298                    symbols,
299                );
300            }
301            Expr::Try(expr_try) => {
302                self.extract_events_from_expr(
303                    &expr_try.expr,
304                    file_path,
305                    type_resolver,
306                    events,
307                    symbols,
308                );
309            }
310            _ => {}
311        }
312    }
313
314    /// Handle method call expressions, looking for emit() and emit_to()
315    fn handle_method_call(
316        &self,
317        method_call: &ExprMethodCall,
318        file_path: &Path,
319        type_resolver: &mut TypeResolver,
320        events: &mut Vec<EventInfo>,
321        symbols: &mut SymbolTable,
322    ) {
323        let method_name = method_call.method.to_string();
324
325        if method_name == "emit" || method_name == "emit_to" {
326            // Check if the receiver looks like app/window (basic heuristic)
327            if self.is_likely_tauri_emitter(&method_call.receiver) {
328                self.extract_emit_event(method_call, file_path, type_resolver, events, symbols);
329            }
330        }
331
332        // Recursively check receiver and arguments for nested emits
333        self.extract_events_from_expr(
334            &method_call.receiver,
335            file_path,
336            type_resolver,
337            events,
338            symbols,
339        );
340        for arg in &method_call.args {
341            self.extract_events_from_expr(arg, file_path, type_resolver, events, symbols);
342        }
343    }
344
345    /// Check if the receiver expression is likely a Tauri emitter (app/window)
346    /// This checks the expression pattern to identify Tauri framework types
347    fn is_likely_tauri_emitter(&self, receiver: &Expr) -> bool {
348        match receiver {
349            Expr::Path(path) => {
350                // Check if this is a path that could be a Tauri type
351                let segments = &path.path.segments;
352
353                // Check for fully qualified paths: tauri::AppHandle, tauri::WebviewWindow
354                if segments.len() >= 2 && segments[0].ident == "tauri" {
355                    let second = &segments[1].ident;
356                    return second == "AppHandle"
357                        || second == "Window"
358                        || second == "WebviewWindow";
359                }
360
361                // Check for simple identifiers - be conservative
362                // Only match very specific common patterns for Tauri types
363                if let Some(ident) = path.path.get_ident() {
364                    let name = ident.to_string();
365                    // Only match exact common parameter names used in Tauri commands
366                    // Avoid matching user variables with similar names
367                    return name == "app" || name == "window" || name == "webview";
368                }
369
370                // For complex paths, check if any segment looks like a Tauri type
371                for segment in segments {
372                    let seg_name = segment.ident.to_string();
373                    if seg_name == "AppHandle" || seg_name == "WebviewWindow" {
374                        return true;
375                    }
376                }
377
378                false
379            }
380            Expr::Field(field_expr) => {
381                // Check field access like self.app
382                if let syn::Member::Named(ident) = &field_expr.member {
383                    let name = ident.to_string();
384                    // Only match exact common field names
385                    return name == "app" || name == "window" || name == "webview";
386                }
387                false
388            }
389            Expr::MethodCall(_) => {
390                // Could be something like get_app().emit()
391                // Be permissive here since method calls that return handles are common
392                true
393            }
394            _ => false,
395        }
396    }
397
398    /// Extract event information from an emit or emit_to call
399    fn extract_emit_event(
400        &self,
401        method_call: &ExprMethodCall,
402        file_path: &Path,
403        type_resolver: &mut TypeResolver,
404        events: &mut Vec<EventInfo>,
405        symbols: &SymbolTable,
406    ) {
407        let method_name = method_call.method.to_string();
408        let args = &method_call.args;
409
410        let (event_name, payload_expr) = if method_name == "emit_to" {
411            // emit_to(label, event_name, payload)
412            if args.len() >= 3 {
413                (self.extract_string_literal(&args[1]), Some(&args[2]))
414            } else {
415                return;
416            }
417        } else {
418            // emit(event_name, payload)
419            if args.len() >= 2 {
420                (self.extract_string_literal(&args[0]), Some(&args[1]))
421            } else {
422                return;
423            }
424        };
425
426        if let Some(event_name) = event_name {
427            let payload_type = if let Some(payload_expr) = payload_expr {
428                self.infer_payload_type(payload_expr, symbols)
429            } else {
430                "()".to_string()
431            };
432
433            let line_number = method_call.method.span().start().line;
434            let payload_type_structure = type_resolver.parse_type_structure(&payload_type);
435
436            events.push(EventInfo {
437                event_name,
438                payload_type,
439                payload_type_structure,
440                file_path: file_path.to_string_lossy().to_string(),
441                line_number,
442            });
443        }
444    }
445
446    /// Extract a string literal from an expression
447    fn extract_string_literal(&self, expr: &Expr) -> Option<String> {
448        if let Expr::Lit(expr_lit) = expr {
449            if let Lit::Str(lit_str) = &expr_lit.lit {
450                return Some(lit_str.value());
451            }
452        }
453        None
454    }
455
456    /// Infer the type of the payload expression
457    /// Uses symbol table to resolve variable names to their types
458    fn infer_payload_type(&self, expr: &Expr, symbols: &SymbolTable) -> String {
459        match expr {
460            // Reference to a variable: &some_var
461            Expr::Reference(expr_ref) => {
462                // Try to infer from the inner expression
463                self.infer_payload_type(&expr_ref.expr, symbols)
464            }
465            // Struct construction: User { ... } or Enum::Variant { ... }
466            Expr::Struct(expr_struct) => {
467                let segments = &expr_struct.path.segments;
468                if segments.len() >= 2 {
469                    // It's likely an Enum variant: MyEnum::MyVariant { ... }
470                    return segments[0].ident.to_string();
471                } else if let Some(segment) = segments.last() {
472                    return segment.ident.to_string();
473                }
474                "unknown".to_string()
475            }
476            // Variable or path: some_var, module::Type
477            Expr::Path(path) => {
478                if let Some(ident) = path.path.get_ident() {
479                    let name = ident.to_string();
480                    // Look up variable in symbol table
481                    if let Some(typ) = symbols.get(&name) {
482                        return typ.clone();
483                    }
484                    // Fallback: might be a type name used directly (like Status::Active)
485                    return name;
486                }
487                // For qualified paths, return the last segment
488                if let Some(segment) = path.path.segments.last() {
489                    return segment.ident.to_string();
490                }
491                "unknown".to_string()
492            }
493            // Tuple: (a, b, c)
494            Expr::Tuple(tuple) => {
495                if tuple.elems.is_empty() {
496                    return "()".to_string();
497                }
498                // For now, just mark as tuple
499                "tuple".to_string()
500            }
501            // Literal values
502            Expr::Lit(lit) => match &lit.lit {
503                Lit::Str(_) => "String".to_string(),
504                Lit::Int(_) => "i32".to_string(),
505                Lit::Float(_) => "f64".to_string(),
506                Lit::Bool(_) => "bool".to_string(),
507                _ => "unknown".to_string(),
508            },
509            // Clone call: var.clone()
510            Expr::MethodCall(method_call) => {
511                let method_name = method_call.method.to_string();
512                if method_name == "clone" {
513                    // Try to infer type from the receiver
514                    return self.infer_payload_type(&method_call.receiver, symbols);
515                }
516                // Can't easily infer return type without type checker
517                "unknown".to_string()
518            }
519            // Function calls
520            Expr::Call(call) => {
521                // Check if this is a struct-like variant or constructor: Type::Variant(...)
522                if let Expr::Path(path) = &*call.func {
523                    if path.path.segments.len() >= 2 {
524                        return path.path.segments[0].ident.to_string();
525                    }
526                }
527                "unknown".to_string()
528            }
529            _ => "unknown".to_string(),
530        }
531    }
532}
533
534impl Default for EventParser {
535    fn default() -> Self {
536        Self::new()
537    }
538}
539
540#[cfg(test)]
541mod tests {
542    use super::*;
543    use syn::parse_quote;
544
545    // extract_string_literal tests
546    mod extract_string_literal {
547        use super::*;
548
549        #[test]
550        fn test_extract_from_string_literal() {
551            let parser = EventParser::new();
552            let expr: Expr = parse_quote!("hello");
553            assert_eq!(
554                parser.extract_string_literal(&expr),
555                Some("hello".to_string())
556            );
557        }
558
559        #[test]
560        fn test_extract_from_non_string() {
561            let parser = EventParser::new();
562            let expr: Expr = parse_quote!(42);
563            assert_eq!(parser.extract_string_literal(&expr), None);
564        }
565
566        #[test]
567        fn test_extract_from_variable() {
568            let parser = EventParser::new();
569            let expr: Expr = parse_quote!(some_var);
570            assert_eq!(parser.extract_string_literal(&expr), None);
571        }
572    }
573
574    // infer_payload_type tests
575    mod infer_payload_type {
576        use super::*;
577
578        #[test]
579        fn test_infer_from_struct_construction() {
580            let parser = EventParser::new();
581            let symbols = SymbolTable::new();
582            let expr: Expr = parse_quote!(User {
583                name: "test".to_string()
584            });
585            assert_eq!(parser.infer_payload_type(&expr, &symbols), "User");
586        }
587
588        #[test]
589        fn test_infer_from_variable_with_symbol_table() {
590            let parser = EventParser::new();
591            let mut symbols = SymbolTable::new();
592            symbols.insert("update".to_string(), "ProgressUpdate".to_string());
593
594            let expr: Expr = parse_quote!(update);
595            assert_eq!(parser.infer_payload_type(&expr, &symbols), "ProgressUpdate");
596        }
597
598        #[test]
599        fn test_infer_from_reference_with_symbol_table() {
600            let parser = EventParser::new();
601            let mut symbols = SymbolTable::new();
602            symbols.insert("update".to_string(), "ProgressUpdate".to_string());
603
604            let expr: Expr = parse_quote!(&update);
605            assert_eq!(parser.infer_payload_type(&expr, &symbols), "ProgressUpdate");
606        }
607
608        #[test]
609        fn test_infer_from_clone_with_symbol_table() {
610            let parser = EventParser::new();
611            let mut symbols = SymbolTable::new();
612            symbols.insert("update".to_string(), "ProgressUpdate".to_string());
613
614            let expr: Expr = parse_quote!(update.clone());
615            assert_eq!(parser.infer_payload_type(&expr, &symbols), "ProgressUpdate");
616        }
617
618        #[test]
619        fn test_infer_from_variable_without_symbol() {
620            let parser = EventParser::new();
621            let symbols = SymbolTable::new();
622            let expr: Expr = parse_quote!(some_var);
623            // Without symbol table entry, returns the variable name
624            assert_eq!(parser.infer_payload_type(&expr, &symbols), "some_var");
625        }
626
627        #[test]
628        fn test_infer_from_string_literal() {
629            let parser = EventParser::new();
630            let symbols = SymbolTable::new();
631            let expr: Expr = parse_quote!("hello");
632            assert_eq!(parser.infer_payload_type(&expr, &symbols), "String");
633        }
634
635        #[test]
636        fn test_infer_from_integer_literal() {
637            let parser = EventParser::new();
638            let symbols = SymbolTable::new();
639            let expr: Expr = parse_quote!(42);
640            assert_eq!(parser.infer_payload_type(&expr, &symbols), "i32");
641        }
642
643        #[test]
644        fn test_infer_from_bool_literal() {
645            let parser = EventParser::new();
646            let symbols = SymbolTable::new();
647            let expr: Expr = parse_quote!(true);
648            assert_eq!(parser.infer_payload_type(&expr, &symbols), "bool");
649        }
650
651        #[test]
652        fn test_infer_from_empty_tuple() {
653            let parser = EventParser::new();
654            let symbols = SymbolTable::new();
655            let expr: Expr = parse_quote!(());
656            assert_eq!(parser.infer_payload_type(&expr, &symbols), "()");
657        }
658    }
659
660    // extract_param_types tests
661    mod extract_param_types {
662        use super::*;
663
664        #[test]
665        fn test_extract_simple_param() {
666            let parser = EventParser::new();
667            let func: syn::ItemFn = parse_quote! {
668                fn test(name: String) {}
669            };
670            let mut symbols = SymbolTable::new();
671            parser.extract_param_types(&func.sig.inputs, &mut symbols);
672
673            assert_eq!(symbols.get("name"), Some(&"String".to_string()));
674        }
675
676        #[test]
677        fn test_extract_reference_param() {
678            let parser = EventParser::new();
679            let func: syn::ItemFn = parse_quote! {
680                fn test(update: &ProgressUpdate) {}
681            };
682            let mut symbols = SymbolTable::new();
683            parser.extract_param_types(&func.sig.inputs, &mut symbols);
684
685            assert_eq!(symbols.get("update"), Some(&"ProgressUpdate".to_string()));
686        }
687
688        #[test]
689        fn test_extract_mutable_reference_param() {
690            let parser = EventParser::new();
691            let func: syn::ItemFn = parse_quote! {
692                fn test(update: &mut ProgressUpdate) {}
693            };
694            let mut symbols = SymbolTable::new();
695            parser.extract_param_types(&func.sig.inputs, &mut symbols);
696
697            assert_eq!(symbols.get("update"), Some(&"ProgressUpdate".to_string()));
698        }
699
700        #[test]
701        fn test_extract_multiple_params() {
702            let parser = EventParser::new();
703            let func: syn::ItemFn = parse_quote! {
704                fn test(app: AppHandle, update: &ProgressUpdate, count: i32) {}
705            };
706            let mut symbols = SymbolTable::new();
707            parser.extract_param_types(&func.sig.inputs, &mut symbols);
708
709            assert_eq!(symbols.get("app"), Some(&"AppHandle".to_string()));
710            assert_eq!(symbols.get("update"), Some(&"ProgressUpdate".to_string()));
711            assert_eq!(symbols.get("count"), Some(&"i32".to_string()));
712        }
713    }
714
715    // is_likely_tauri_emitter tests
716    mod is_likely_tauri_emitter {
717        use super::*;
718
719        #[test]
720        fn test_app_identifier() {
721            let parser = EventParser::new();
722            let expr: Expr = parse_quote!(app);
723            assert!(parser.is_likely_tauri_emitter(&expr));
724        }
725
726        #[test]
727        fn test_window_identifier() {
728            let parser = EventParser::new();
729            let expr: Expr = parse_quote!(window);
730            assert!(parser.is_likely_tauri_emitter(&expr));
731        }
732
733        #[test]
734        fn test_webview_identifier() {
735            let parser = EventParser::new();
736            let expr: Expr = parse_quote!(webview);
737            assert!(parser.is_likely_tauri_emitter(&expr));
738        }
739
740        #[test]
741        fn test_qualified_tauri_app_handle() {
742            let parser = EventParser::new();
743            let expr: Expr = parse_quote!(tauri::AppHandle);
744            assert!(parser.is_likely_tauri_emitter(&expr));
745        }
746
747        #[test]
748        fn test_qualified_tauri_window() {
749            let parser = EventParser::new();
750            let expr: Expr = parse_quote!(tauri::Window);
751            assert!(parser.is_likely_tauri_emitter(&expr));
752        }
753
754        #[test]
755        fn test_self_app_field() {
756            let parser = EventParser::new();
757            let expr: Expr = parse_quote!(self.app);
758            assert!(parser.is_likely_tauri_emitter(&expr));
759        }
760
761        #[test]
762        fn test_random_variable_not_emitter() {
763            let parser = EventParser::new();
764            let expr: Expr = parse_quote!(some_var);
765            assert!(!parser.is_likely_tauri_emitter(&expr));
766        }
767
768        #[test]
769        fn test_method_call_is_permissive() {
770            let parser = EventParser::new();
771            // Method calls like self.get_app() are permissive
772            let expr: Expr = parse_quote!(self.get_app());
773            assert!(parser.is_likely_tauri_emitter(&expr));
774        }
775    }
776
777    // Integration tests for event extraction
778    mod event_extraction {
779        use super::*;
780        use crate::analysis::type_resolver::TypeResolver;
781
782        #[test]
783        fn test_extract_event_with_struct_payload() {
784            let parser = EventParser::new();
785            let mut type_resolver = TypeResolver::new();
786
787            let file: SynFile = parse_quote! {
788                fn emit_progress(app: AppHandle) {
789                    app.emit("progress", ProgressUpdate { value: 50 }).unwrap();
790                }
791            };
792
793            let events = parser
794                .extract_events_from_ast(&file, Path::new("test.rs"), &mut type_resolver)
795                .unwrap();
796
797            assert_eq!(events.len(), 1);
798            assert_eq!(events[0].event_name, "progress");
799            assert_eq!(events[0].payload_type, "ProgressUpdate");
800        }
801
802        #[test]
803        fn test_extract_event_with_variable_payload_from_param() {
804            let parser = EventParser::new();
805            let mut type_resolver = TypeResolver::new();
806
807            let file: SynFile = parse_quote! {
808                fn emit_externally(app: AppHandle, update: &ProgressUpdate) {
809                    app.emit("progress-update", update).unwrap();
810                }
811            };
812
813            let events = parser
814                .extract_events_from_ast(&file, Path::new("test.rs"), &mut type_resolver)
815                .unwrap();
816
817            assert_eq!(events.len(), 1);
818            assert_eq!(events[0].event_name, "progress-update");
819            assert_eq!(events[0].payload_type, "ProgressUpdate");
820        }
821
822        #[test]
823        fn test_extract_emit_to_with_variable_payload() {
824            let parser = EventParser::new();
825            let mut type_resolver = TypeResolver::new();
826
827            let file: SynFile = parse_quote! {
828                fn emit_to_window(app: AppHandle, update: &ProgressUpdate) {
829                    app.emit_to("main", "progress-update", update).unwrap();
830                }
831            };
832
833            let events = parser
834                .extract_events_from_ast(&file, Path::new("test.rs"), &mut type_resolver)
835                .unwrap();
836
837            assert_eq!(events.len(), 1);
838            assert_eq!(events[0].event_name, "progress-update");
839            assert_eq!(events[0].payload_type, "ProgressUpdate");
840        }
841
842        #[test]
843        fn test_extract_event_with_cloned_variable() {
844            let parser = EventParser::new();
845            let mut type_resolver = TypeResolver::new();
846
847            let file: SynFile = parse_quote! {
848                fn emit_cloned(app: AppHandle, update: ProgressUpdate) {
849                    app.emit("progress", update.clone()).unwrap();
850                }
851            };
852
853            let events = parser
854                .extract_events_from_ast(&file, Path::new("test.rs"), &mut type_resolver)
855                .unwrap();
856
857            assert_eq!(events.len(), 1);
858            assert_eq!(events[0].event_name, "progress");
859            assert_eq!(events[0].payload_type, "ProgressUpdate");
860        }
861    }
862}