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(
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 for item in &ast.items {
34 if let syn::Item::Fn(func) = item {
35 let mut symbols = SymbolTable::new();
37 self.extract_param_types(&func.sig.inputs, &mut symbols);
38
39 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 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 fn extract_type_name(&self, ty: &Type) -> String {
72 match ty {
73 Type::Reference(type_ref) => {
74 self.extract_type_name(&type_ref.elem)
76 }
77 Type::Path(type_path) => {
78 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 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 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 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 fn extract_local_binding(&self, local: &syn::Local, symbols: &mut SymbolTable) {
136 if let Pat::Ident(pat_ident) = &local.pat {
138 let var_name = pat_ident.ident.to_string();
139
140 if let Some(local_init) = &local.init {
142 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 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 fn infer_type_from_init(&self, expr: &Expr, symbols: &SymbolTable) -> String {
164 match expr {
165 Expr::Struct(expr_struct) => {
166 if let Some(segment) = expr_struct.path.segments.last() {
168 return segment.ident.to_string();
169 }
170 }
171 Expr::Call(call) => {
172 if let Expr::Path(path) = &*call.func {
174 if path.path.segments.len() >= 2 {
176 return path.path.segments[0].ident.to_string();
177 }
178 }
179 }
180 Expr::Path(path) => {
181 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 return self.infer_type_from_init(&expr_ref.expr, symbols);
192 }
193 _ => {}
194 }
195 "unknown".to_string()
196 }
197
198 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 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 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 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 fn is_likely_tauri_emitter(&self, receiver: &Expr) -> bool {
332 match receiver {
333 Expr::Path(path) => {
334 let segments = &path.path.segments;
336
337 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 if let Some(ident) = path.path.get_ident() {
348 let name = ident.to_string();
349 return name == "app" || name == "window" || name == "webview";
352 }
353
354 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 if let syn::Member::Named(ident) = &field_expr.member {
367 let name = ident.to_string();
368 return name == "app" || name == "window" || name == "webview";
370 }
371 false
372 }
373 Expr::MethodCall(_) => {
374 true
377 }
378 _ => false,
379 }
380 }
381
382 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 if args.len() >= 3 {
397 (self.extract_string_literal(&args[1]), Some(&args[2]))
398 } else {
399 return;
400 }
401 } else {
402 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 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 fn infer_payload_type(&self, expr: &Expr, symbols: &SymbolTable) -> String {
443 match expr {
444 Expr::Reference(expr_ref) => {
446 self.infer_payload_type(&expr_ref.expr, symbols)
448 }
449 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 Expr::Path(path) => {
458 if let Some(ident) = path.path.get_ident() {
459 let name = ident.to_string();
460 if let Some(typ) = symbols.get(&name) {
462 return typ.clone();
463 }
464 return name;
466 }
467 if let Some(segment) = path.path.segments.last() {
469 return segment.ident.to_string();
470 }
471 "unknown".to_string()
472 }
473 Expr::Tuple(tuple) => {
475 if tuple.elems.is_empty() {
476 return "()".to_string();
477 }
478 "tuple".to_string()
480 }
481 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 Expr::MethodCall(method_call) => {
491 let method_name = method_call.method.to_string();
492 if method_name == "clone" {
493 return self.infer_payload_type(&method_call.receiver, symbols);
495 }
496 "unknown".to_string()
498 }
499 Expr::Call(_) => {
501 "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 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 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 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 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 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 let expr: Expr = parse_quote!(self.get_app());
748 assert!(parser.is_likely_tauri_emitter(&expr));
749 }
750 }
751
752 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}