1use crate::analysis::type_resolver::TypeResolver;
2use crate::models::EventInfo;
3use std::path::Path;
4use syn::{Expr, ExprMethodCall, File as SynFile, Lit};
5
6#[derive(Debug)]
8pub struct EventParser;
9
10impl EventParser {
11 pub fn new() -> Self {
12 Self
13 }
14
15 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 for item in &ast.items {
30 if let syn::Item::Fn(func) = item {
31 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 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 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 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 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 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 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 fn is_likely_tauri_emitter(&self, receiver: &Expr) -> bool {
175 match receiver {
176 Expr::Path(path) => {
177 let segments = &path.path.segments;
179
180 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 if let Some(ident) = path.path.get_ident() {
191 let name = ident.to_string();
192 return name == "app" || name == "window" || name == "webview";
195 }
196
197 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 if let syn::Member::Named(ident) = &field_expr.member {
210 let name = ident.to_string();
211 return name == "app" || name == "window" || name == "webview";
213 }
214 false
215 }
216 Expr::MethodCall(_) => {
217 true
220 }
221 _ => false,
222 }
223 }
224
225 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 if args.len() >= 3 {
239 (self.extract_string_literal(&args[1]), Some(&args[2]))
240 } else {
241 return;
242 }
243 } else {
244 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 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 fn infer_payload_type(expr: &Expr) -> String {
285 match expr {
286 Expr::Reference(expr_ref) => {
288 Self::infer_payload_type(&expr_ref.expr)
290 }
291 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 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 Expr::Tuple(tuple) => {
307 if tuple.elems.is_empty() {
308 return "()".to_string();
309 }
310 "tuple".to_string()
312 }
313 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 Expr::Call(_) | Expr::MethodCall(_) => {
323 "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}