ts_rs_macros_serde_json/
lib.rs

1#![macro_use]
2#![deny(unused)]
3
4use proc_macro2::{Ident, TokenStream};
5use quote::{format_ident, quote};
6use syn::{
7    parse_quote, spanned::Spanned, ConstParam, GenericParam, Generics, Item, LifetimeParam, Result,
8    TypeParam, WhereClause,
9};
10
11use crate::deps::Dependencies;
12
13#[macro_use]
14mod utils;
15mod attr;
16mod deps;
17mod types;
18
19struct DerivedTS {
20    name: String,
21    inline: TokenStream,
22    decl: TokenStream,
23    inline_flattened: Option<TokenStream>,
24    dependencies: Dependencies,
25
26    export: bool,
27    export_to: Option<String>,
28}
29
30impl DerivedTS {
31    fn generate_export_test(&self, rust_ty: &Ident, generics: &Generics) -> Option<TokenStream> {
32        let test_fn = format_ident!("export_bindings_{}", &self.name.to_lowercase());
33        let generic_params = generics
34            .params
35            .iter()
36            .filter(|param| matches!(param, GenericParam::Type(_)))
37            .map(|_| quote! { () });
38        let ty = quote!(<#rust_ty<#(#generic_params),*> as ts_rs::TS>);
39
40        Some(quote! {
41            #[cfg(test)]
42            #[test]
43            fn #test_fn() {
44                #ty::export().expect("could not export type");
45            }
46        })
47    }
48
49    fn into_impl(self, rust_ty: Ident, generics: Generics) -> TokenStream {
50        let export_to = match &self.export_to {
51            Some(dirname) if dirname.ends_with('/') => {
52                format!("{}{}.ts", dirname, self.name)
53            }
54            Some(filename) => filename.clone(),
55            None => {
56                format!("bindings/{}.ts", self.name)
57            }
58        };
59
60        let export = match self.export {
61            true => Some(self.generate_export_test(&rust_ty, &generics)),
62            false => None,
63        };
64
65        let DerivedTS {
66            name,
67            inline,
68            decl,
69            inline_flattened,
70            dependencies,
71            ..
72        } = self;
73        let inline_flattened = inline_flattened
74            .map(|t| {
75                quote! {
76                    fn inline_flattened() -> String {
77                        #t
78                    }
79                }
80            })
81            .unwrap_or_else(TokenStream::new);
82
83        let impl_start = generate_impl(&rust_ty, &generics);
84        quote! {
85            #impl_start {
86                const EXPORT_TO: Option<&'static str> = Some(#export_to);
87
88                fn decl() -> String {
89                    #decl
90                }
91                fn name() -> String {
92                    #name.to_owned()
93                }
94                fn inline() -> String {
95                    #inline
96                }
97                #inline_flattened
98                fn dependencies() -> Vec<ts_rs::Dependency>
99                where
100                    Self: 'static,
101                {
102                    #dependencies
103                }
104                fn transparent() -> bool {
105                    false
106                }
107            }
108
109            #export
110        }
111    }
112}
113
114// generate start of the `impl TS for #ty` block, up to (excluding) the open brace
115fn generate_impl(ty: &Ident, generics: &Generics) -> TokenStream {
116    use GenericParam::*;
117
118    let bounds = generics.params.iter().map(|param| match param {
119        Type(TypeParam {
120            ident,
121            colon_token,
122            bounds,
123            ..
124        }) => quote!(#ident #colon_token #bounds),
125        Lifetime(LifetimeParam {
126            lifetime,
127            colon_token,
128            bounds,
129            ..
130        }) => quote!(#lifetime #colon_token #bounds),
131        Const(ConstParam {
132            const_token,
133            ident,
134            colon_token,
135            ty,
136            ..
137        }) => quote!(#const_token #ident #colon_token #ty),
138    });
139    let type_args = generics.params.iter().map(|param| match param {
140        Type(TypeParam { ident, .. }) | Const(ConstParam { ident, .. }) => quote!(#ident),
141        Lifetime(LifetimeParam { lifetime, .. }) => quote!(#lifetime),
142    });
143
144    let where_bound = add_ts_to_where_clause(generics);
145    quote!(impl <#(#bounds),*> ts_rs::TS for #ty <#(#type_args),*> #where_bound)
146}
147
148fn add_ts_to_where_clause(generics: &Generics) -> Option<WhereClause> {
149    let generic_types = generics
150        .params
151        .iter()
152        .filter_map(|gp| match gp {
153            GenericParam::Type(ty) => Some(ty.ident.clone()),
154            _ => None,
155        })
156        .collect::<Vec<_>>();
157    if generic_types.is_empty() {
158        return generics.where_clause.clone();
159    }
160    match generics.where_clause {
161        None => Some(parse_quote! { where #( #generic_types : ts_rs::TS ),* }),
162        Some(ref w) => {
163            let bounds = w.predicates.iter();
164            Some(parse_quote! { where #(#bounds,)* #( #generic_types : ts_rs::TS ),* })
165        }
166    }
167}
168
169/// Derives [TS](./trait.TS.html) for a struct or enum.
170/// Please take a look at [TS](./trait.TS.html) for documentation.
171#[proc_macro_derive(TS, attributes(ts))]
172pub fn typescript(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
173    match entry(input) {
174        Err(err) => err.to_compile_error(),
175        Ok(result) => result,
176    }
177    .into()
178}
179
180fn entry(input: proc_macro::TokenStream) -> Result<TokenStream> {
181    let input = syn::parse::<Item>(input)?;
182    let (ts, ident, generics) = match input {
183        Item::Struct(s) => (types::struct_def(&s)?, s.ident, s.generics),
184        Item::Enum(e) => (types::enum_def(&e)?, e.ident, e.generics),
185        _ => syn_err!(input.span(); "unsupported item"),
186    };
187
188    Ok(ts.into_impl(ident, generics))
189}