tauri_typegen/analysis/
event_parser.rs

1use crate::analysis::type_resolver::TypeResolver;
2use crate::models::EventInfo;
3use std::path::Path;
4use syn::{Expr, ExprMethodCall, File as SynFile, Lit};
5
6/// Parser for Tauri event emissions
7#[derive(Debug)]
8pub struct EventParser;
9
10impl EventParser {
11    pub fn new() -> Self {
12        Self
13    }
14
15    /// Extract event emissions from a cached AST
16    /// Looks for patterns like:
17    /// - app.emit("event-name", payload)
18    /// - window.emit("event-name", payload)
19    /// - app.emit_to("label", "event-name", payload)
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
28        // Visit all items in the AST looking for emit calls
29        for item in &ast.items {
30            if let syn::Item::Fn(func) = item {
31                // Search within function bodies
32                self.extract_events_from_block(
33                    &func.block.stmts,
34                    file_path,
35                    type_resolver,
36                    &mut events,
37                );
38            }
39        }
40
41        Ok(events)
42    }
43
44    /// Recursively search through statements for emit calls
45    fn extract_events_from_block(
46        &self,
47        stmts: &[syn::Stmt],
48        file_path: &Path,
49        type_resolver: &mut TypeResolver,
50        events: &mut Vec<EventInfo>,
51    ) {
52        for stmt in stmts {
53            self.extract_events_from_stmt(stmt, file_path, type_resolver, events);
54        }
55    }
56
57    /// Extract events from a single statement
58    fn extract_events_from_stmt(
59        &self,
60        stmt: &syn::Stmt,
61        file_path: &Path,
62        type_resolver: &mut TypeResolver,
63        events: &mut Vec<EventInfo>,
64    ) {
65        match stmt {
66            syn::Stmt::Expr(expr, _) => {
67                self.extract_events_from_expr(expr, file_path, type_resolver, events);
68            }
69            syn::Stmt::Local(local) => {
70                if let Some(init) = &local.init {
71                    self.extract_events_from_expr(&init.expr, file_path, type_resolver, events);
72                }
73            }
74            _ => {}
75        }
76    }
77
78    /// Extract events from an expression
79    fn extract_events_from_expr(
80        &self,
81        expr: &Expr,
82        file_path: &Path,
83        type_resolver: &mut TypeResolver,
84        events: &mut Vec<EventInfo>,
85    ) {
86        match expr {
87            Expr::MethodCall(method_call) => {
88                self.handle_method_call(method_call, file_path, type_resolver, events);
89            }
90            Expr::Block(block) => {
91                self.extract_events_from_block(
92                    &block.block.stmts,
93                    file_path,
94                    type_resolver,
95                    events,
96                );
97            }
98            Expr::If(expr_if) => {
99                self.extract_events_from_block(
100                    &expr_if.then_branch.stmts,
101                    file_path,
102                    type_resolver,
103                    events,
104                );
105                if let Some((_, else_branch)) = &expr_if.else_branch {
106                    self.extract_events_from_expr(else_branch, file_path, type_resolver, events);
107                }
108            }
109            Expr::Match(expr_match) => {
110                for arm in &expr_match.arms {
111                    self.extract_events_from_expr(&arm.body, file_path, type_resolver, events);
112                }
113            }
114            Expr::Loop(expr_loop) => {
115                self.extract_events_from_block(
116                    &expr_loop.body.stmts,
117                    file_path,
118                    type_resolver,
119                    events,
120                );
121            }
122            Expr::While(expr_while) => {
123                self.extract_events_from_block(
124                    &expr_while.body.stmts,
125                    file_path,
126                    type_resolver,
127                    events,
128                );
129            }
130            Expr::ForLoop(expr_for) => {
131                self.extract_events_from_block(
132                    &expr_for.body.stmts,
133                    file_path,
134                    type_resolver,
135                    events,
136                );
137            }
138            Expr::Await(expr_await) => {
139                self.extract_events_from_expr(&expr_await.base, file_path, type_resolver, events);
140            }
141            Expr::Try(expr_try) => {
142                self.extract_events_from_expr(&expr_try.expr, file_path, type_resolver, events);
143            }
144            _ => {}
145        }
146    }
147
148    /// Handle method call expressions, looking for emit() and emit_to()
149    fn handle_method_call(
150        &self,
151        method_call: &ExprMethodCall,
152        file_path: &Path,
153        type_resolver: &mut TypeResolver,
154        events: &mut Vec<EventInfo>,
155    ) {
156        let method_name = method_call.method.to_string();
157
158        if method_name == "emit" || method_name == "emit_to" {
159            // Check if the receiver looks like app/window (basic heuristic)
160            if self.is_likely_tauri_emitter(&method_call.receiver) {
161                self.extract_emit_event(method_call, file_path, type_resolver, events);
162            }
163        }
164
165        // Recursively check receiver and arguments for nested emits
166        self.extract_events_from_expr(&method_call.receiver, file_path, type_resolver, events);
167        for arg in &method_call.args {
168            self.extract_events_from_expr(arg, file_path, type_resolver, events);
169        }
170    }
171
172    /// Check if the receiver expression is likely a Tauri emitter (app/window)
173    /// This checks the expression pattern to identify Tauri framework types
174    fn is_likely_tauri_emitter(&self, receiver: &Expr) -> bool {
175        match receiver {
176            Expr::Path(path) => {
177                // Check if this is a path that could be a Tauri type
178                let segments = &path.path.segments;
179
180                // Check for fully qualified paths: tauri::AppHandle, tauri::WebviewWindow
181                if segments.len() >= 2 && segments[0].ident == "tauri" {
182                    let second = &segments[1].ident;
183                    return second == "AppHandle"
184                        || second == "Window"
185                        || second == "WebviewWindow";
186                }
187
188                // Check for simple identifiers - be conservative
189                // Only match very specific common patterns for Tauri types
190                if let Some(ident) = path.path.get_ident() {
191                    let name = ident.to_string();
192                    // Only match exact common parameter names used in Tauri commands
193                    // Avoid matching user variables with similar names
194                    return name == "app" || name == "window" || name == "webview";
195                }
196
197                // For complex paths, check if any segment looks like a Tauri type
198                for segment in segments {
199                    let seg_name = segment.ident.to_string();
200                    if seg_name == "AppHandle" || seg_name == "WebviewWindow" {
201                        return true;
202                    }
203                }
204
205                false
206            }
207            Expr::Field(field_expr) => {
208                // Check field access like self.app
209                if let syn::Member::Named(ident) = &field_expr.member {
210                    let name = ident.to_string();
211                    // Only match exact common field names
212                    return name == "app" || name == "window" || name == "webview";
213                }
214                false
215            }
216            Expr::MethodCall(_) => {
217                // Could be something like get_app().emit()
218                // Be permissive here since method calls that return handles are common
219                true
220            }
221            _ => false,
222        }
223    }
224
225    /// Extract event information from an emit or emit_to call
226    fn extract_emit_event(
227        &self,
228        method_call: &ExprMethodCall,
229        file_path: &Path,
230        type_resolver: &mut TypeResolver,
231        events: &mut Vec<EventInfo>,
232    ) {
233        let method_name = method_call.method.to_string();
234        let args = &method_call.args;
235
236        let (event_name, payload_expr) = if method_name == "emit_to" {
237            // emit_to(label, event_name, payload)
238            if args.len() >= 3 {
239                (self.extract_string_literal(&args[1]), Some(&args[2]))
240            } else {
241                return;
242            }
243        } else {
244            // emit(event_name, payload)
245            if args.len() >= 2 {
246                (self.extract_string_literal(&args[0]), Some(&args[1]))
247            } else {
248                return;
249            }
250        };
251
252        if let Some(event_name) = event_name {
253            let payload_type = if let Some(payload_expr) = payload_expr {
254                Self::infer_payload_type(payload_expr)
255            } else {
256                "()".to_string()
257            };
258
259            let typescript_payload_type = type_resolver.map_rust_type_to_typescript(&payload_type);
260            let line_number = method_call.method.span().start().line;
261
262            events.push(EventInfo {
263                event_name,
264                payload_type,
265                typescript_payload_type,
266                file_path: file_path.to_string_lossy().to_string(),
267                line_number,
268            });
269        }
270    }
271
272    /// Extract a string literal from an expression
273    fn extract_string_literal(&self, expr: &Expr) -> Option<String> {
274        if let Expr::Lit(expr_lit) = expr {
275            if let Lit::Str(lit_str) = &expr_lit.lit {
276                return Some(lit_str.value());
277            }
278        }
279        None
280    }
281
282    /// Infer the type of the payload expression
283    /// This is a best-effort heuristic based on the expression structure
284    fn infer_payload_type(expr: &Expr) -> String {
285        match expr {
286            // Reference to a variable: &some_var
287            Expr::Reference(expr_ref) => {
288                // Try to infer from the inner expression
289                Self::infer_payload_type(&expr_ref.expr)
290            }
291            // Struct construction: User { ... }
292            Expr::Struct(expr_struct) => {
293                if let Some(segment) = expr_struct.path.segments.last() {
294                    return segment.ident.to_string();
295                }
296                "unknown".to_string()
297            }
298            // Variable or path: some_var, module::Type
299            Expr::Path(path) => {
300                if let Some(segment) = path.path.segments.last() {
301                    return segment.ident.to_string();
302                }
303                "unknown".to_string()
304            }
305            // Tuple: (a, b, c)
306            Expr::Tuple(tuple) => {
307                if tuple.elems.is_empty() {
308                    return "()".to_string();
309                }
310                // For now, just mark as tuple
311                "tuple".to_string()
312            }
313            // Literal values
314            Expr::Lit(lit) => match &lit.lit {
315                Lit::Str(_) => "String".to_string(),
316                Lit::Int(_) => "i32".to_string(),
317                Lit::Float(_) => "f64".to_string(),
318                Lit::Bool(_) => "bool".to_string(),
319                _ => "unknown".to_string(),
320            },
321            // Method or function calls
322            Expr::Call(_) | Expr::MethodCall(_) => {
323                // Can't easily infer return type without type checker
324                "unknown".to_string()
325            }
326            _ => "unknown".to_string(),
327        }
328    }
329}
330
331impl Default for EventParser {
332    fn default() -> Self {
333        Self::new()
334    }
335}