1#![warn(missing_docs)]
2
3use proc_macro::TokenStream;
4use quote::quote;
5use syn::{DeriveInput, ItemFn, parse_macro_input};
6
7#[proc_macro_derive(TypescriptClass, attributes(ts))]
38pub fn typescript_class_derive(input: TokenStream) -> TokenStream {
39 let input = parse_macro_input!(input as DeriveInput);
40
41 let struct_name = &input.ident;
43
44 let fields = match &input.data {
46 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
47 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unnamed(_), .. }) => {
48 return syn::Error::new_spanned(input, "Tuple structs are not supported").to_compile_error().into();
49 }
50 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unit, .. }) => {
51 return syn::Error::new_spanned(input, "Unit structs are not supported").to_compile_error().into();
52 }
53 syn::Data::Enum(_) => {
54 return syn::Error::new_spanned(input, "Enums are not supported").to_compile_error().into();
55 }
56 syn::Data::Union(_) => {
57 return syn::Error::new_spanned(input, "Unions are not supported").to_compile_error().into();
58 }
59 };
60
61 for field in fields {
63 if field.ident.is_none() {
64 return syn::Error::new_spanned(field, "All fields must have names").to_compile_error().into();
65 }
66 }
67
68 let field_types: Vec<String> = fields
70 .iter()
71 .map(|field| {
72 let field_name = field.ident.as_ref().unwrap();
73 let field_ty = &field.ty;
74
75 let ts_type = match quote!(#field_ty).to_string().as_str() {
77 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
78 "bool" => "boolean",
79 "String" | "&str" => "string",
80 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>" | "Option<i32>"
81 | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
82 "Option<bool>" => "boolean | undefined",
83 "Option<String>" | "Option<&str>" => "string | undefined",
84 _ => "any",
85 };
86
87 format!(" {}: {}", field_name, ts_type)
88 })
89 .collect();
90
91 let constructor_params: Vec<String> = fields
93 .iter()
94 .map(|field| {
95 let field_name = field.ident.as_ref().unwrap();
96 let field_ty = &field.ty;
97
98 let ts_type = match quote!(#field_ty).to_string().as_str() {
99 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
100 "bool" => "boolean",
101 "String" | "&str" => "string",
102 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>" | "Option<i32>"
103 | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
104 "Option<bool>" => "boolean | undefined",
105 "Option<String>" | "Option<&str>" => "string | undefined",
106 _ => "any",
107 };
108
109 format!(" {}: {}", field_name, ts_type)
110 })
111 .collect();
112
113 let constructor_assignments: Vec<String> = fields
115 .iter()
116 .map(|field| {
117 let field_name = field.ident.as_ref().unwrap();
118 format!(" this.{} = {};", field_name, field_name)
119 })
120 .collect();
121
122 let field_types_str = field_types.join(";\n");
124 let constructor_params_str = constructor_params.join(",\n");
125 let constructor_assignments_str = constructor_assignments.join("\n");
126
127 let ts_code = quote! {
128 impl #struct_name {
129 pub const TS_CLASS_DEFINITION: &'static str = concat!(
131 "class ", stringify!(#struct_name), " {\n",
132 #field_types_str, ";\n",
133 "\n",
134 " constructor(\n",
135 #constructor_params_str, ",\n",
136 " ) {\n",
137 #constructor_assignments_str, "\n",
138 " }\n",
139 "}\n"
140 );
141
142 pub fn ts_class_definition() -> &'static str {
144 Self::TS_CLASS_DEFINITION
145 }
146 }
147 };
148
149 TokenStream::from(ts_code)
150}
151
152#[proc_macro_attribute]
171pub fn TypescriptFunction(_args: TokenStream, input: TokenStream) -> TokenStream {
172 let input = parse_macro_input!(input as ItemFn);
173
174 let fn_name = &input.sig.ident;
176
177 let params = &input.sig.inputs;
179 let param_types: Vec<String> = params
180 .iter()
181 .map(|param| match param {
182 syn::FnArg::Typed(pat_type) => {
183 let param_name = match &*pat_type.pat {
184 syn::Pat::Ident(pat_ident) => &pat_ident.ident,
185 _ => return "_: any".to_string(),
186 };
187 let param_ty = &pat_type.ty;
188
189 let ts_type = match quote!(#param_ty).to_string().as_str() {
190 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
191 "bool" => "boolean",
192 "String" | "&str" => "string",
193 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>"
194 | "Option<i32>" | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
195 "Option<bool>" => "boolean | undefined",
196 "Option<String>" | "Option<&str>" => "string | undefined",
197 _ => "any",
198 };
199
200 format!("{}: {}", param_name, ts_type)
201 }
202 _ => "_: any".to_string(),
203 })
204 .collect();
205
206 let return_type = match &input.sig.output {
208 syn::ReturnType::Type(_, ty) => match quote!(#ty).to_string().as_str() {
209 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
210 "bool" => "boolean",
211 "String" | "&str" => "string",
212 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>" | "Option<i32>"
213 | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
214 "Option<bool>" => "boolean | undefined",
215 "Option<String>" | "Option<&str>" => "string | undefined",
216 _ => "any",
217 },
218 syn::ReturnType::Default => "void",
219 };
220
221 let param_types_str = param_types.join(", ");
223 let ts_const_name = format!("{}_TS_FUNCTION_DEFINITION", fn_name);
224 let ts_const_ident = syn::Ident::new(&ts_const_name, fn_name.span());
225 let ts_function_name = syn::Ident::new(&format!("{}_ts_function_definition", fn_name), fn_name.span());
226
227 let ts_code = quote! {
228 #input
229
230 pub const #ts_const_ident: &'static str = concat!(
232 "type ", stringify!(#fn_name), "Function = (",
233 #param_types_str,
234 ") => ",
235 #return_type,
236 ";\n"
237 );
238
239 pub fn #ts_function_name() -> &'static str {
241 #ts_const_ident
242 }
243 };
244
245 TokenStream::from(ts_code)
246}
247
248#[proc_macro_derive(TypescriptInterface, attributes(ts))]
273pub fn typescript_interface_derive(input: TokenStream) -> TokenStream {
274 let input = parse_macro_input!(input as DeriveInput);
275
276 let trait_name = &input.ident;
278
279 if !matches!(input.data, syn::Data::Struct(_)) {
281 return syn::Error::new_spanned(input, "Only structs are supported for TypescriptInterface").to_compile_error().into();
282 }
283
284 let fields = match &input.data {
286 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
287 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unnamed(_), .. }) => {
288 return syn::Error::new_spanned(input, "Tuple structs are not supported").to_compile_error().into();
289 }
290 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unit, .. }) => {
291 return syn::Error::new_spanned(input, "Unit structs are not supported").to_compile_error().into();
292 }
293 _ => unreachable!(),
294 };
295
296 let interface_fields: Vec<String> = fields
298 .iter()
299 .map(|field| {
300 let field_name = field.ident.as_ref().unwrap();
301 let field_ty = &field.ty;
302
303 let ts_type = match quote!(#field_ty).to_string().as_str() {
305 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
306 "bool" => "boolean",
307 "String" | "&str" => "string",
308 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>" | "Option<i32>"
309 | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
310 "Option<bool>" => "boolean | undefined",
311 "Option<String>" | "Option<&str>" => "string | undefined",
312 _ => "any",
313 };
314
315 format!(" {}: {}", field_name, ts_type)
316 })
317 .collect();
318
319 let interface_fields_str = interface_fields.join(";\n");
321
322 let ts_code = quote! {
323 impl #trait_name {
324 pub const TS_INTERFACE_DEFINITION: &'static str = concat!(
326 "interface ", stringify!(#trait_name), " {\n",
327 #interface_fields_str, ";\n",
328 "}\n"
329 );
330
331 pub fn ts_interface_definition() -> &'static str {
333 Self::TS_INTERFACE_DEFINITION
334 }
335 }
336 };
337
338 TokenStream::from(ts_code)
339}
340
341#[proc_macro_derive(TypescriptEnum, attributes(ts))]
391pub fn typescript_enum_derive(input: TokenStream) -> TokenStream {
392 let input = parse_macro_input!(input as DeriveInput);
393
394 let enum_name = &input.ident;
396
397 let variants = match &input.data {
399 syn::Data::Enum(syn::DataEnum { variants, .. }) => variants,
400 _ => {
401 return syn::Error::new_spanned(input, "Only enums are supported").to_compile_error().into();
402 }
403 };
404
405 let enum_variants: Vec<String> = variants
407 .iter()
408 .enumerate()
409 .map(|(index, variant)| {
410 let variant_name = &variant.ident;
411
412 Ok(match &variant.fields {
414 syn::Fields::Unit => {
415 format!(" {} = {}", variant_name, index)
417 }
418 syn::Fields::Unnamed(fields) if fields.unnamed.len() == 1 => {
419 let value = &fields.unnamed[0];
421 let value_str = quote!(#value).to_string();
422 format!(" {} = {}", variant_name, value_str)
423 }
424 _ => {
425 return Err(syn::Error::new_spanned(
426 variant,
427 "Only unit variants or variants with a single value are supported",
428 ));
429 }
430 })
431 })
432 .collect::<Result<Vec<_>, _>>()
433 .unwrap();
434
435 let enum_variants_str = enum_variants.join(",\n");
437
438 let ts_code = quote! {
439 impl #enum_name {
440 pub const TS_ENUM_DEFINITION: &'static str = concat!(
442 "enum ", stringify!(#enum_name), " {\n",
443 #enum_variants_str,
444 "\n}"
445 );
446
447 pub fn ts_enum_definition() -> &'static str {
449 Self::TS_ENUM_DEFINITION
450 }
451 }
452 };
453
454 TokenStream::from(ts_code)
455}
456
457#[proc_macro_derive(TypescriptType, attributes(ts))]
482pub fn typescript_type_derive(input: TokenStream) -> TokenStream {
483 let input = parse_macro_input!(input as DeriveInput);
484
485 let type_name = &input.ident;
487
488 if !matches!(input.data, syn::Data::Struct(_)) {
490 return syn::Error::new_spanned(input, "Only structs are supported for TypescriptType").to_compile_error().into();
491 }
492
493 let fields = match &input.data {
495 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Named(fields), .. }) => &fields.named,
496 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unnamed(_), .. }) => {
497 return syn::Error::new_spanned(input, "Tuple structs are not supported").to_compile_error().into();
498 }
499 syn::Data::Struct(syn::DataStruct { fields: syn::Fields::Unit, .. }) => {
500 return syn::Error::new_spanned(input, "Unit structs are not supported").to_compile_error().into();
501 }
502 _ => unreachable!(),
503 };
504
505 let type_fields: Vec<String> = fields
507 .iter()
508 .map(|field| {
509 let field_name = field.ident.as_ref().unwrap();
510 let field_ty = &field.ty;
511
512 let ts_type = match quote!(#field_ty).to_string().as_str() {
514 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
515 "bool" => "boolean",
516 "String" | "&str" => "string",
517 "Option<u8>" | "Option<u16>" | "Option<u32>" | "Option<u64>" | "Option<i8>" | "Option<i16>" | "Option<i32>"
518 | "Option<i64>" | "Option<f32>" | "Option<f64>" => "number | undefined",
519 "Option<bool>" => "boolean | undefined",
520 "Option<String>" | "Option<&str>" => "string | undefined",
521 _ => "any",
522 };
523
524 format!(" {}: {}", field_name, ts_type)
525 })
526 .collect();
527
528 let type_fields_str = type_fields.join(";\n");
530
531 let ts_code = quote! {
532 impl #type_name {
533 pub const TS_TYPE_DEFINITION: &'static str = concat!(
535 "type ", stringify!(#type_name), " = {\n",
536 #type_fields_str, ";\n",
537 "}\n"
538 );
539
540 pub fn ts_type_definition() -> &'static str {
542 Self::TS_TYPE_DEFINITION
543 }
544 }
545 };
546
547 TokenStream::from(ts_code)
548}