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 _ => {
48 return syn::Error::new_spanned(input, "Only named fields structs are supported").to_compile_error().into();
49 }
50 };
51
52 let field_types: Vec<String> = fields
54 .iter()
55 .map(|field| {
56 let field_name = field.ident.as_ref().unwrap();
57 let field_ty = &field.ty;
58
59 let ts_type = match quote!(#field_ty).to_string().as_str() {
61 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
62 "bool" => "boolean",
63 "String" | "&str" => "string",
64 _ => "any",
65 };
66
67 format!(" {}: {}", field_name, ts_type)
68 })
69 .collect();
70
71 let constructor_params: Vec<String> = fields
73 .iter()
74 .map(|field| {
75 let field_name = field.ident.as_ref().unwrap();
76 let field_ty = &field.ty;
77
78 let ts_type = match quote!(#field_ty).to_string().as_str() {
79 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
80 "bool" => "boolean",
81 "String" | "&str" => "string",
82 _ => "any",
83 };
84
85 format!(" {}: {}", field_name, ts_type)
86 })
87 .collect();
88
89 let constructor_assignments: Vec<String> = fields
91 .iter()
92 .map(|field| {
93 let field_name = field.ident.as_ref().unwrap();
94 format!(" this.{} = {};", field_name, field_name)
95 })
96 .collect();
97
98 let field_types_str = field_types.join(";\n");
100 let constructor_params_str = constructor_params.join(",\n");
101 let constructor_assignments_str = constructor_assignments.join("\n");
102
103 let ts_code = quote! {
104 impl #struct_name {
105 pub const TS_CLASS_DEFINITION: &'static str = concat!(
107 "class ", stringify!(#struct_name), " {\n",
108 #field_types_str, ";\n",
109 "\n",
110 " constructor(\n",
111 #constructor_params_str, ",\n",
112 " ) {\n",
113 #constructor_assignments_str, "\n",
114 " }\n",
115 "}\n"
116 );
117 }
118 };
119
120 TokenStream::from(ts_code)
121}
122
123#[proc_macro_attribute]
142pub fn TypescriptFunction(_args: TokenStream, input: TokenStream) -> TokenStream {
143 let input = parse_macro_input!(input as ItemFn);
144
145 let fn_name = &input.sig.ident;
147
148 let params = &input.sig.inputs;
150 let param_types: Vec<String> = params
151 .iter()
152 .map(|param| match param {
153 syn::FnArg::Typed(pat_type) => {
154 let param_name = match &*pat_type.pat {
155 syn::Pat::Ident(pat_ident) => &pat_ident.ident,
156 _ => return "_: any".to_string(),
157 };
158 let param_ty = &pat_type.ty;
159
160 let ts_type = match quote!(#param_ty).to_string().as_str() {
161 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
162 "bool" => "boolean",
163 "String" | "&str" => "string",
164 _ => "any",
165 };
166
167 format!("{}: {}", param_name, ts_type)
168 }
169 _ => "_: any".to_string(),
170 })
171 .collect();
172
173 let return_type = match &input.sig.output {
175 syn::ReturnType::Type(_, ty) => match quote!(#ty).to_string().as_str() {
176 "u8" | "u16" | "u32" | "u64" | "i8" | "i16" | "i32" | "i64" | "f32" | "f64" => "number",
177 "bool" => "boolean",
178 "String" | "&str" => "string",
179 _ => "any",
180 },
181 syn::ReturnType::Default => "void",
182 };
183
184 let param_types_str = param_types.join(", ");
186 let ts_const_name = format!("{}_TS_FUNCTION_DEFINITION", fn_name);
187 let ts_const_ident = syn::Ident::new(&ts_const_name, fn_name.span());
188
189 let ts_code = quote! {
190 #input
191
192 pub const #ts_const_ident: &'static str = concat!(
194 "type ", stringify!(#fn_name), "Function = (",
195 #param_types_str,
196 ") => ",
197 #return_type,
198 ";\n"
199 );
200 };
201
202 TokenStream::from(ts_code)
203}