tauri_typegen/analysis/
command_parser.rs

1use crate::analysis::type_resolver::TypeResolver;
2use crate::models::{CommandInfo, ParameterInfo};
3use std::path::Path;
4use syn::{File as SynFile, FnArg, ItemFn, PatType, ReturnType, Type};
5
6/// Parser for Tauri command functions
7#[derive(Debug)]
8pub struct CommandParser;
9
10impl CommandParser {
11    pub fn new() -> Self {
12        Self
13    }
14
15    /// Extract commands from a cached AST
16    pub fn extract_commands_from_ast(
17        &self,
18        ast: &SynFile,
19        file_path: &Path,
20        type_resolver: &mut TypeResolver,
21    ) -> Result<Vec<CommandInfo>, Box<dyn std::error::Error>> {
22        let commands = ast
23            .items
24            .iter()
25            .filter_map(|item| {
26                if let syn::Item::Fn(func) = item {
27                    if self.is_tauri_command(func) {
28                        return self.extract_command_info(func, file_path, type_resolver);
29                    }
30                }
31                None
32            })
33            .collect();
34
35        Ok(commands)
36    }
37
38    /// Check if a function is a Tauri command
39    fn is_tauri_command(&self, func: &ItemFn) -> bool {
40        func.attrs.iter().any(|attr| {
41            attr.path().segments.len() == 2
42                && attr.path().segments[0].ident == "tauri"
43                && attr.path().segments[1].ident == "command"
44                || attr.path().is_ident("command")
45        })
46    }
47
48    /// Extract command information from a function
49    fn extract_command_info(
50        &self,
51        func: &ItemFn,
52        file_path: &Path,
53        type_resolver: &mut TypeResolver,
54    ) -> Option<CommandInfo> {
55        let name = func.sig.ident.to_string();
56        let parameters = self.extract_parameters(&func.sig.inputs, type_resolver);
57        let return_type = self.extract_return_type(&func.sig.output, type_resolver);
58        let is_async = func.sig.asyncness.is_some();
59
60        // Get line number from the function's span
61        let line_number = func.sig.ident.span().start().line;
62
63        Some(CommandInfo {
64            name,
65            parameters,
66            return_type,
67            file_path: file_path.to_string_lossy().to_string(),
68            line_number,
69            is_async,
70        })
71    }
72
73    /// Extract parameters from function signature
74    fn extract_parameters(
75        &self,
76        inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
77        type_resolver: &mut TypeResolver,
78    ) -> Vec<ParameterInfo> {
79        inputs
80            .iter()
81            .filter_map(|input| {
82                if let FnArg::Typed(PatType { pat, ty, .. }) = input {
83                    if let syn::Pat::Ident(pat_ident) = pat.as_ref() {
84                        let name = pat_ident.ident.to_string();
85                        let rust_type = self.type_to_string(ty);
86
87                        // Skip Tauri-specific parameters
88                        if self.is_tauri_parameter(&name, &rust_type) {
89                            return None;
90                        }
91
92                        let typescript_type = type_resolver.map_rust_type_to_typescript(&rust_type);
93                        let is_optional = self.is_optional_type(ty);
94
95                        return Some(ParameterInfo {
96                            name,
97                            rust_type,
98                            typescript_type,
99                            is_optional,
100                        });
101                    }
102                }
103                None
104            })
105            .collect()
106    }
107
108    /// Check if a parameter is a Tauri-specific parameter that should be skipped
109    fn is_tauri_parameter(&self, name: &str, rust_type: &str) -> bool {
110        // Common Tauri parameter names
111        if matches!(name, "app" | "window" | "state" | "handle") {
112            return true;
113        }
114
115        // Common Tauri parameter types
116        if rust_type.contains("AppHandle")
117            || rust_type.contains("Window")
118            || rust_type.contains("State")
119            || rust_type.contains("Manager")
120        {
121            return true;
122        }
123
124        false
125    }
126
127    /// Extract return type from function signature
128    fn extract_return_type(&self, output: &ReturnType, type_resolver: &mut TypeResolver) -> String {
129        match output {
130            ReturnType::Default => "void".to_string(),
131            ReturnType::Type(_, ty) => {
132                let rust_type = self.type_to_string(ty);
133                type_resolver.map_rust_type_to_typescript(&rust_type)
134            }
135        }
136    }
137
138    /// Convert a Type to its string representation
139    fn type_to_string(&self, ty: &Type) -> String {
140        match ty {
141            Type::Path(type_path) => {
142                let segments: Vec<String> = type_path
143                    .path
144                    .segments
145                    .iter()
146                    .map(|segment| {
147                        if segment.arguments.is_empty() {
148                            segment.ident.to_string()
149                        } else {
150                            match &segment.arguments {
151                                syn::PathArguments::AngleBracketed(args) => {
152                                    let inner_types: Vec<String> = args
153                                        .args
154                                        .iter()
155                                        .filter_map(|arg| {
156                                            if let syn::GenericArgument::Type(inner_ty) = arg {
157                                                Some(self.type_to_string(inner_ty))
158                                            } else {
159                                                None
160                                            }
161                                        })
162                                        .collect();
163                                    format!("{}<{}>", segment.ident, inner_types.join(", "))
164                                }
165                                _ => segment.ident.to_string(),
166                            }
167                        }
168                    })
169                    .collect();
170                segments.join("::")
171            }
172            Type::Reference(type_ref) => {
173                format!("&{}", self.type_to_string(&type_ref.elem))
174            }
175            Type::Tuple(type_tuple) => {
176                if type_tuple.elems.is_empty() {
177                    "()".to_string()
178                } else {
179                    let types: Vec<String> = type_tuple
180                        .elems
181                        .iter()
182                        .map(|t| self.type_to_string(t))
183                        .collect();
184                    format!("({})", types.join(", "))
185                }
186            }
187            _ => "unknown".to_string(),
188        }
189    }
190
191    /// Check if a type is Option<T>
192    fn is_optional_type(&self, ty: &Type) -> bool {
193        if let Type::Path(type_path) = ty {
194            if let Some(segment) = type_path.path.segments.last() {
195                return segment.ident == "Option";
196            }
197        }
198        false
199    }
200}
201
202impl Default for CommandParser {
203    fn default() -> Self {
204        Self::new()
205    }
206}