tauri_typegen/analysis/
channel_parser.rs1use crate::analysis::type_resolver::TypeResolver;
2use crate::models::ChannelInfo;
3use std::path::Path;
4use syn::spanned::Spanned;
5use syn::{
6 AngleBracketedGenericArguments, FnArg, GenericArgument, ItemFn, PathArguments, PathSegment,
7 Type,
8};
9
10#[derive(Debug)]
12pub struct ChannelParser;
13
14impl ChannelParser {
15 pub fn new() -> Self {
16 Self
17 }
18
19 pub fn extract_channels_from_command(
22 &self,
23 func: &ItemFn,
24 command_name: &str,
25 file_path: &Path,
26 type_resolver: &mut TypeResolver,
27 ) -> Result<Vec<ChannelInfo>, Box<dyn std::error::Error>> {
28 let mut channels = Vec::new();
29
30 for input in &func.sig.inputs {
32 if let FnArg::Typed(pat_type) = input {
33 let param_name = if let syn::Pat::Ident(pat_ident) = &*pat_type.pat {
35 pat_ident.ident.to_string()
36 } else {
37 continue;
38 };
39
40 if let Some(message_type) = self.extract_channel_message_type(&pat_type.ty) {
42 let typescript_message_type =
44 type_resolver.map_rust_type_to_typescript(&message_type);
45
46 let line_number = pat_type.ty.span().start().line;
48
49 channels.push(ChannelInfo {
50 parameter_name: param_name,
51 message_type: message_type.clone(),
52 typescript_message_type,
53 command_name: command_name.to_string(),
54 file_path: file_path.to_string_lossy().to_string(),
55 line_number,
56 });
57 }
58 }
59 }
60
61 Ok(channels)
62 }
63
64 fn extract_channel_message_type(&self, ty: &Type) -> Option<String> {
67 match ty {
68 Type::Path(type_path) => {
69 let last_segment = type_path.path.segments.last()?;
71
72 if self.is_channel_segment(last_segment, &type_path.path.segments) {
74 if let PathArguments::AngleBracketed(AngleBracketedGenericArguments {
76 args,
77 ..
78 }) = &last_segment.arguments
79 {
80 if let Some(GenericArgument::Type(inner_type)) = args.first() {
81 return Some(Self::type_to_string(inner_type));
82 }
83 }
84 }
85 None
86 }
87 _ => None,
88 }
89 }
90
91 fn is_channel_segment(
94 &self,
95 segment: &PathSegment,
96 all_segments: &syn::punctuated::Punctuated<PathSegment, syn::Token![::]>,
97 ) -> bool {
98 let segment_name = segment.ident.to_string();
99
100 if segment_name != "Channel" {
102 return false;
103 }
104
105 if all_segments.len() == 1 {
107 return true;
109 }
110
111 if all_segments.len() >= 2 {
113 let first = all_segments.first().unwrap().ident.to_string();
114 if first == "tauri" {
115 return true;
116 }
117 }
118
119 false
120 }
121
122 fn type_to_string(ty: &Type) -> String {
125 match ty {
126 Type::Path(type_path) => {
127 let segments: Vec<String> = type_path
128 .path
129 .segments
130 .iter()
131 .map(|seg| {
132 let ident = seg.ident.to_string();
133 if let PathArguments::AngleBracketed(args) = &seg.arguments {
135 let generic_args: Vec<String> = args
136 .args
137 .iter()
138 .filter_map(|arg| {
139 if let GenericArgument::Type(t) = arg {
140 Some(Self::type_to_string(t))
141 } else {
142 None
143 }
144 })
145 .collect();
146 if !generic_args.is_empty() {
147 return format!("{}<{}>", ident, generic_args.join(", "));
148 }
149 }
150 ident
151 })
152 .collect();
153 segments.join("::")
154 }
155 Type::Reference(type_ref) => {
156 format!("&{}", Self::type_to_string(&type_ref.elem))
157 }
158 Type::Tuple(tuple) => {
159 if tuple.elems.is_empty() {
160 "()".to_string()
161 } else {
162 let types: Vec<String> = tuple.elems.iter().map(Self::type_to_string).collect();
163 format!("({})", types.join(", "))
164 }
165 }
166 Type::Array(arr) => {
167 format!("[{}]", Self::type_to_string(&arr.elem))
168 }
169 _ => "unknown".to_string(),
170 }
171 }
172}
173
174impl Default for ChannelParser {
175 fn default() -> Self {
176 Self::new()
177 }
178}
179
180#[cfg(test)]
181mod tests {
182 use super::*;
183 use syn::parse_quote;
184
185 #[test]
186 fn test_detect_simple_channel() {
187 let parser = ChannelParser::new();
188 let ty: Type = parse_quote!(Channel<ProgressUpdate>);
189 let result = parser.extract_channel_message_type(&ty);
190 assert_eq!(result, Some("ProgressUpdate".to_string()));
191 }
192
193 #[test]
194 fn test_detect_qualified_channel() {
195 let parser = ChannelParser::new();
196 let ty: Type = parse_quote!(tauri::ipc::Channel<DownloadEvent>);
197 let result = parser.extract_channel_message_type(&ty);
198 assert_eq!(result, Some("DownloadEvent".to_string()));
199 }
200
201 #[test]
202 fn test_detect_channel_with_primitive() {
203 let parser = ChannelParser::new();
204 let ty: Type = parse_quote!(Channel<i32>);
205 let result = parser.extract_channel_message_type(&ty);
206 assert_eq!(result, Some("i32".to_string()));
207 }
208
209 #[test]
210 fn test_non_channel_type() {
211 let parser = ChannelParser::new();
212 let ty: Type = parse_quote!(String);
213 let result = parser.extract_channel_message_type(&ty);
214 assert_eq!(result, None);
215 }
216
217 #[test]
218 fn test_channel_with_complex_type() {
219 let parser = ChannelParser::new();
220 let ty: Type = parse_quote!(Channel<Vec<String>>);
221 let result = parser.extract_channel_message_type(&ty);
222 assert_eq!(result, Some("Vec<String>".to_string()));
223 }
224
225 #[test]
226 fn test_type_to_string() {
227 let _parser = ChannelParser::new();
228
229 let ty: Type = parse_quote!(String);
230 assert_eq!(ChannelParser::type_to_string(&ty), "String");
231
232 let ty: Type = parse_quote!(Vec<i32>);
233 assert_eq!(ChannelParser::type_to_string(&ty), "Vec<i32>");
234
235 let ty: Type = parse_quote!(Option<User>);
236 assert_eq!(ChannelParser::type_to_string(&ty), "Option<User>");
237 }
238}