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 })
71 }
72
73 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 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 fn is_tauri_parameter(&self, name: &str, rust_type: &str) -> bool {
110 if matches!(name, "app" | "window" | "state" | "handle") {
112 return true;
113 }
114
115 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 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 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 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}