1use std::path::PathBuf;
2
3use lang::TSExporter;
4use parsers::handle_export_type_parsing;
5use proc_macro::TokenStream;
6use syn::{parse_macro_input, Attribute, DeriveInput};
7
8mod case;
9mod error;
10mod exporter;
11mod lang;
12mod parsers;
13
14use error::ToCompileError;
15
16use case::*;
17use error::*;
18use exporter::*;
19
20pub(crate) static DEFAULT_EXPORT_PATH: &str = "exports";
21
22#[proc_macro_derive(ExportType, attributes(export_type))]
44pub fn export_type(input: TokenStream) -> TokenStream {
45 let input = parse_macro_input!(input as DeriveInput);
46 match handle_export_type(input.clone()) {
47 Ok(_output) => {
48 #[cfg(not(test))]
49 if let Ok(export_path) = get_export_path_from_attrs(&input.attrs) {
50 if let Ok(out_dir) = std::env::var("OUT_DIR") {
52 let out_path = PathBuf::from(out_dir);
53 let _ = create_exporter_files(out_path.join("types"));
54 }
55
56 if std::env::var("CARGO_PUBLISH").is_err() && !std::env::var("OUT_DIR").is_ok() {
58 let _ = create_exporter_files(export_path);
59 }
60 }
61 #[cfg(test)]
62 if let Ok(export_path) = get_export_path_from_attrs(&input.attrs) {
63 let _ = create_exporter_files(export_path);
65 }
66 quote::quote! {}.into()
67 }
68 Err(e) => e.to_compile_error().into(),
69 }
70}
71
72fn handle_export_type(input: DeriveInput) -> TSTypeResult<proc_macro2::TokenStream> {
73 let lang = get_lang_from_attrs(&input.attrs)?;
74 let mut output = handle_export_type_parsing(&input, &lang)?;
75 let name = input.ident.to_string();
76 let generics = get_generics_from_attrs(&input.attrs)?;
77 output.generics = generics;
78 output.lang = lang;
79 output.name = name;
80 add_struct_or_enum(output)?;
81 Ok(quote::quote! {})
82}
83
84fn get_exporter_from_lang(
85 lang: &str,
86 output: Output,
87 generics: Vec<String>,
88) -> TSTypeResult<Box<dyn ToOutput>> {
89 match lang {
90 "typescript" | "ts" => Ok(Box::new(TSExporter::new(output, None, generics))),
91 lang => Err(TSTypeError::UnsupportedLanguage(lang.to_string())),
92 }
93}
94
95fn get_generics_from_attrs(attrs: &[Attribute]) -> TSTypeResult<Vec<String>> {
96 let mut generics = vec![];
97
98 for attr in attrs {
99 if attr.path().is_ident("export_type") {
100 if let Ok(nested) = attr.parse_args_with(
101 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
102 ) {
103 for meta in nested {
104 if let syn::Meta::NameValue(nv) = meta {
105 if nv.path.is_ident("generics") {
106 if let syn::Expr::Lit(syn::ExprLit {
107 lit: syn::Lit::Str(lit_str),
108 ..
109 }) = nv.value
110 {
111 generics = lit_str
112 .value()
113 .split(',')
114 .map(|s| s.trim().to_string())
115 .collect();
116 }
117 }
118 }
119 }
120 }
121 }
122 }
123
124 Ok(generics)
125}
126
127fn get_lang_from_attrs(attrs: &[Attribute]) -> TSTypeResult<String> {
128 let mut lang = String::from("typescript");
129
130 for attr in attrs {
131 if attr.path().is_ident("export_type") {
132 if let Ok(nested) = attr.parse_args_with(
133 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
134 ) {
135 for meta in nested {
136 if let syn::Meta::NameValue(nv) = meta {
137 if nv.path.is_ident("lang") {
138 if let syn::Expr::Lit(syn::ExprLit {
139 lit: syn::Lit::Str(lit_str),
140 ..
141 }) = nv.value
142 {
143 lang = lit_str.value();
144 }
145 }
146 }
147 }
148 }
149 }
150 }
151
152 Ok(lang)
153}
154
155fn get_export_path_from_attrs(attrs: &[Attribute]) -> TSTypeResult<PathBuf> {
156 let mut export_path = PathBuf::from(DEFAULT_EXPORT_PATH);
157
158 for attr in attrs {
159 if attr.path().is_ident("export_type") {
160 if let Ok(nested) = attr.parse_args_with(
161 syn::punctuated::Punctuated::<syn::Meta, syn::Token![,]>::parse_terminated,
162 ) {
163 for meta in nested {
164 if let syn::Meta::NameValue(nv) = meta {
165 if nv.path.is_ident("path") {
166 if let syn::Expr::Lit(syn::ExprLit {
167 lit: syn::Lit::Str(lit_str),
168 ..
169 }) = nv.value
170 {
171 export_path = PathBuf::from(lit_str.value());
172 }
173 }
174 }
175 }
176 }
177 }
178 }
179
180 Ok(export_path)
181}
182
183#[cfg(test)]
184mod tests {
185 use super::*;
186 use syn::parse_quote;
187
188 #[test]
189 fn test_get_lang_from_attrs() {
190 let attrs = vec![];
191 assert_eq!(
192 get_lang_from_attrs(&attrs).unwrap(),
193 "typescript".to_string()
194 );
195
196 let attrs = vec![parse_quote! { #[export_type(lang = "typescript")] }];
197 assert_eq!(
198 get_lang_from_attrs(&attrs).unwrap(),
199 "typescript".to_string()
200 );
201
202 let attrs = vec![parse_quote! { #[export_type(lang = "unsupported")] }];
203 assert_eq!(
204 get_lang_from_attrs(&attrs).unwrap(),
205 "unsupported".to_string()
206 );
207 }
208
209 #[test]
210 fn test_get_export_path_from_attrs() {
211 let attrs = vec![];
212 assert_eq!(
213 get_export_path_from_attrs(&attrs).unwrap(),
214 PathBuf::from("exports")
215 );
216
217 let attrs = vec![parse_quote! { #[export_type(path = "test")] }];
218 assert_eq!(
219 get_export_path_from_attrs(&attrs).unwrap(),
220 PathBuf::from("test")
221 );
222 }
223}