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