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#[derive(Debug)]
9pub struct EventParser;
10
11type SymbolTable = HashMap<String, String>;
13
14impl EventParser {
15 pub fn new() -> Self {
16 Self
17 }
18
19 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 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 let mut symbols = SymbolTable::new();
44 self.extract_param_types(&func.sig.inputs, &mut symbols);
45
46 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 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 fn extract_type_name(&self, ty: &Type) -> String {
84 match ty {
85 Type::Reference(type_ref) => {
86 self.extract_type_name(&type_ref.elem)
88 }
89 Type::Path(type_path) => {
90 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 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 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 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 fn extract_local_binding(&self, local: &syn::Local, symbols: &mut SymbolTable) {
148 if let Pat::Ident(pat_ident) = &local.pat {
150 let var_name = pat_ident.ident.to_string();
151
152 if let Some(local_init) = &local.init {
154 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 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 fn infer_type_from_init(&self, expr: &Expr, symbols: &SymbolTable) -> String {
176 match expr {
177 Expr::Struct(expr_struct) => {
178 let segments = &expr_struct.path.segments;
180 if segments.len() >= 2 {
181 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 if let Expr::Path(path) = &*call.func {
190 if path.path.segments.len() >= 2 {
192 return path.path.segments[0].ident.to_string();
193 }
194 }
195 }
196 Expr::Path(path) => {
197 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 return self.infer_type_from_init(&expr_ref.expr, symbols);
208 }
209 _ => {}
210 }
211 "unknown".to_string()
212 }
213
214 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 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 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 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 fn is_likely_tauri_emitter(&self, receiver: &Expr) -> bool {
348 match receiver {
349 Expr::Path(path) => {
350 let segments = &path.path.segments;
352
353 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 if let Some(ident) = path.path.get_ident() {
364 let name = ident.to_string();
365 return name == "app" || name == "window" || name == "webview";
368 }
369
370 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 if let syn::Member::Named(ident) = &field_expr.member {
383 let name = ident.to_string();
384 return name == "app" || name == "window" || name == "webview";
386 }
387 false
388 }
389 Expr::MethodCall(_) => {
390 true
393 }
394 _ => false,
395 }
396 }
397
398 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 if args.len() >= 3 {
413 (self.extract_string_literal(&args[1]), Some(&args[2]))
414 } else {
415 return;
416 }
417 } else {
418 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 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 fn infer_payload_type(&self, expr: &Expr, symbols: &SymbolTable) -> String {
459 match expr {
460 Expr::Reference(expr_ref) => {
462 self.infer_payload_type(&expr_ref.expr, symbols)
464 }
465 Expr::Struct(expr_struct) => {
467 let segments = &expr_struct.path.segments;
468 if segments.len() >= 2 {
469 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 Expr::Path(path) => {
478 if let Some(ident) = path.path.get_ident() {
479 let name = ident.to_string();
480 if let Some(typ) = symbols.get(&name) {
482 return typ.clone();
483 }
484 return name;
486 }
487 if let Some(segment) = path.path.segments.last() {
489 return segment.ident.to_string();
490 }
491 "unknown".to_string()
492 }
493 Expr::Tuple(tuple) => {
495 if tuple.elems.is_empty() {
496 return "()".to_string();
497 }
498 "tuple".to_string()
500 }
501 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 Expr::MethodCall(method_call) => {
511 let method_name = method_call.method.to_string();
512 if method_name == "clone" {
513 return self.infer_payload_type(&method_call.receiver, symbols);
515 }
516 "unknown".to_string()
518 }
519 Expr::Call(call) => {
521 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 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 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 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 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 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 let expr: Expr = parse_quote!(self.get_app());
773 assert!(parser.is_likely_tauri_emitter(&expr));
774 }
775 }
776
777 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}