tauri_typegen/analysis/
command_parser.rs1use 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#[derive(Debug)]
8pub struct CommandParser;
9
10impl CommandParser {
11 pub fn new() -> Self {
12 Self
13 }
14
15 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 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 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 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 channels: Vec::new(), })
72 }
73
74 fn extract_parameters(
76 &self,
77 inputs: &syn::punctuated::Punctuated<FnArg, syn::token::Comma>,
78 type_resolver: &mut TypeResolver,
79 ) -> Vec<ParameterInfo> {
80 inputs
81 .iter()
82 .filter_map(|input| {
83 if let FnArg::Typed(PatType { pat, ty, .. }) = input {
84 if let syn::Pat::Ident(pat_ident) = pat.as_ref() {
85 let name = pat_ident.ident.to_string();
86
87 if self.is_tauri_parameter_type(ty) {
89 return None;
90 }
91
92 let rust_type = Self::type_to_string(ty);
93 let typescript_type = type_resolver.map_rust_type_to_typescript(&rust_type);
94 let is_optional = self.is_optional_type(ty);
95
96 return Some(ParameterInfo {
97 name,
98 rust_type,
99 typescript_type,
100 is_optional,
101 });
102 }
103 }
104 None
105 })
106 .collect()
107 }
108
109 fn is_tauri_parameter_type(&self, ty: &Type) -> bool {
112 if let Type::Path(type_path) = ty {
113 let segments = &type_path.path.segments;
114
115 if segments.len() >= 2 {
120 if segments[0].ident == "tauri" {
122 if segments.len() == 2 {
123 let second = &segments[1].ident;
125 return second == "AppHandle"
126 || second == "Window"
127 || second == "WebviewWindow"
128 || second == "State"
129 || second == "Manager";
130 } else if segments.len() == 3 && segments[1].ident == "ipc" {
131 let third = &segments[2].ident;
133 return third == "Request" || third == "Channel";
134 }
135 }
136 }
137
138 if let Some(last_segment) = segments.last() {
140 let type_ident = &last_segment.ident;
141
142 if type_ident == "AppHandle" || type_ident == "WebviewWindow" {
145 return true;
146 }
147
148 if type_ident == "Channel"
150 && matches!(
151 last_segment.arguments,
152 syn::PathArguments::AngleBracketed(_)
153 )
154 {
155 return true;
156 }
157
158 if (type_ident == "State" || type_ident == "Window")
161 && !last_segment.arguments.is_empty()
162 {
163 return true;
164 }
165 }
166 }
167
168 false
169 }
170
171 fn extract_return_type(&self, output: &ReturnType, type_resolver: &mut TypeResolver) -> String {
173 match output {
174 ReturnType::Default => "void".to_string(),
175 ReturnType::Type(_, ty) => {
176 let rust_type = Self::type_to_string(ty);
177 type_resolver.map_rust_type_to_typescript(&rust_type)
178 }
179 }
180 }
181
182 fn type_to_string(ty: &Type) -> String {
184 match ty {
185 Type::Path(type_path) => {
186 let segments: Vec<String> = type_path
187 .path
188 .segments
189 .iter()
190 .map(|segment| {
191 if segment.arguments.is_empty() {
192 segment.ident.to_string()
193 } else {
194 match &segment.arguments {
195 syn::PathArguments::AngleBracketed(args) => {
196 let inner_types: Vec<String> = args
197 .args
198 .iter()
199 .filter_map(|arg| {
200 if let syn::GenericArgument::Type(inner_ty) = arg {
201 Some(Self::type_to_string(inner_ty))
202 } else {
203 None
204 }
205 })
206 .collect();
207 format!("{}<{}>", segment.ident, inner_types.join(", "))
208 }
209 _ => segment.ident.to_string(),
210 }
211 }
212 })
213 .collect();
214 segments.join("::")
215 }
216 Type::Reference(type_ref) => {
217 format!("&{}", Self::type_to_string(&type_ref.elem))
218 }
219 Type::Tuple(type_tuple) => {
220 if type_tuple.elems.is_empty() {
221 "()".to_string()
222 } else {
223 let types: Vec<String> =
224 type_tuple.elems.iter().map(Self::type_to_string).collect();
225 format!("({})", types.join(", "))
226 }
227 }
228 _ => "unknown".to_string(),
229 }
230 }
231
232 fn is_optional_type(&self, ty: &Type) -> bool {
234 if let Type::Path(type_path) = ty {
235 if let Some(segment) = type_path.path.segments.last() {
236 return segment.ident == "Option";
237 }
238 }
239 false
240 }
241}
242
243impl Default for CommandParser {
244 fn default() -> Self {
245 Self::new()
246 }
247}