ts_rs_macros_serde_json/
lib.rs1#![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
114fn 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#[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}