gents_derives/
lib.rs

1use case::{convert_camel_from_pascal, convert_camel_from_snake};
2use syn::{parse_macro_input, DeriveInput};
3mod case;
4mod container;
5mod symbol;
6
7use container::{Container, RenameAll};
8use proc_macro::TokenStream;
9use quote::quote;
10
11use crate::container::GentsWasmAttrs;
12
13#[proc_macro_attribute]
14pub fn gents_header(attr: TokenStream, item: TokenStream) -> TokenStream {
15    let item: proc_macro2::TokenStream = item.into();
16    let attrs = syn::parse2::<GentsWasmAttrs>(attr.into()).expect("parse error, please check");
17    let file_name = attrs.get_file_name();
18    quote! {
19        #[derive(::serde::Serialize, ::serde::Deserialize)]
20        #[cfg_attr(any(test, feature = "gents"), derive(::gents_derives::TS))]
21        #[cfg_attr(
22            any(test, feature = "gents"),
23            ts(file_name = #file_name, rename_all = "camelCase")
24        )]
25        #[serde(rename_all = "camelCase")]
26        #item
27    }
28    .into()
29}
30
31#[proc_macro_derive(TS, attributes(ts))]
32pub fn derive_ts(input: TokenStream) -> TokenStream {
33    let input = parse_macro_input!(input as DeriveInput);
34    get_impl_block(input).into()
35}
36
37fn get_impl_block(input: DeriveInput) -> proc_macro2::TokenStream {
38    let container = Container::from_ast(&input);
39    let file_name = container.file_name;
40    let is_enum = container.is_enum;
41    let rename_all = container.rename_all;
42    let ident = container.ident;
43    let fields = container.fields;
44    let rename = container.rename;
45    let ts_name = match rename {
46        Some(s) => s,
47        None => ident.to_string(),
48    };
49    let comments = container.comments;
50    let register_func = {
51        let field_ds = fields.into_iter().filter(|f| !f.skip).map(|s| {
52            let fi = s.ident;
53            let rename = s.rename;
54            let ty = s.ty;
55            let field_comments = s.comments;
56            let name = match (rename, &rename_all) {
57                (None, None) => fi.to_string(),
58                (None, Some(RenameAll::CamelCase)) => {
59                    let s = fi.to_string();
60                    if is_enum {
61                        convert_camel_from_pascal(s)
62                    } else {
63                        convert_camel_from_snake(s)
64                    }
65                }
66                (Some(s), _) => s,
67            };
68            if let Some(ty) = ty {
69                quote! {
70                    let dep = <#ty as ::gents::TS>::_register(manager);
71                    deps.insert(dep);
72                    let fd = ::gents::FieldDescriptor {
73                        ident: #name.to_string(),
74                        optional: <#ty as ::gents::TS>::_is_optional(),
75                        ts_ty: <#ty as ::gents::TS>::_ts_name(),
76                        comments: vec![#(#field_comments.to_string()),*],
77                    };
78                    fields.push(fd);
79                }
80            } else {
81                quote! {
82                    let fd = ::gents::FieldDescriptor {
83                        ident: #name.to_string(),
84                        optional: false,
85                        ts_ty: String::from(""),
86                        comments: vec![#(#field_comments.to_string()),*],
87                    };
88                    fields.push(fd);
89                }
90            }
91        });
92        let descriptor = if is_enum {
93            quote! {
94                let _enum = ::gents::EnumDescriptor {
95                    dependencies: deps.into_iter().collect(),
96                    fields,
97                    file_name: #file_name.to_string(),
98                    ts_name: #ts_name.to_string(),
99                    comments: vec![#(#comments.to_string()),*],
100                };
101                let descriptor = ::gents::Descriptor::Enum(_enum);
102            }
103        } else {
104            quote! {
105                let deps_vec = deps.into_iter().collect();
106                let _interface = ::gents::InterfaceDescriptor {
107                    dependencies: deps_vec,
108                    fields,
109                    file_name: #file_name.to_string(),
110                    ts_name: #ts_name.to_string(),
111                    comments: vec![#(#comments.to_string()),*],
112                };
113                let descriptor = ::gents::Descriptor::Interface(_interface);
114            }
115        };
116        quote! {
117            fn _register(manager: &mut ::gents::DescriptorManager) -> usize {
118                let type_id = std::any::TypeId::of::<Self>();
119                let mut deps = ::std::collections::HashSet::<usize>::new();
120                let mut fields = vec![];
121                #(#field_ds)*
122                #descriptor
123                manager.registry(type_id, descriptor)
124            }
125        }
126    };
127    let ts_name_func = quote! {
128        fn _ts_name() -> String {
129            #ts_name.to_string()
130        }
131    };
132    quote! {
133        #[cfg(any(test, feature="gents"))]
134        impl ::gents::TS for #ident {
135            #register_func
136            #ts_name_func
137        }
138    }
139}