Skip to main content

ast_node/
lib.rs

1#![deny(clippy::all)]
2#![recursion_limit = "1024"]
3
4extern crate proc_macro;
5
6use quote::quote;
7use swc_macros_common::prelude::*;
8use syn::*;
9
10mod ast_node_macro;
11mod encoding;
12mod enum_deserialize;
13mod spanned;
14
15/// Derives [`swc_common::Spanned`]. See [`swc_common::Spanned`] for
16/// documentation.
17#[proc_macro_derive(Spanned, attributes(span))]
18pub fn derive_spanned(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
19    let input = parse::<DeriveInput>(input).expect("failed to parse input as DeriveInput");
20
21    let item = self::spanned::derive(input);
22
23    print("derive(Spanned)", item.into_token_stream())
24}
25
26/// Derives `serde::Deserialize` which is aware of `tag` based deserialization.
27#[proc_macro_derive(DeserializeEnum, attributes(tag, encoding))]
28pub fn derive_deserialize_enum(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
29    let input = parse::<DeriveInput>(input).expect("failed to parse input as DeriveInput");
30
31    let item = enum_deserialize::expand(input);
32
33    print("derive(DeserializeEnum)", item.into_token_stream())
34}
35
36#[proc_macro_derive(Encode, attributes(encoding))]
37pub fn derive_encode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
38    let input =
39        syn::parse::<syn::DeriveInput>(input).expect("failed to parse input as DeriveInput");
40
41    let item = encoding::encode::expand(input);
42    print("derive(Encode)", item.into_token_stream())
43}
44
45#[proc_macro_derive(Decode, attributes(encoding))]
46pub fn derive_decode(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
47    let input =
48        syn::parse::<syn::DeriveInput>(input).expect("failed to parse input as DeriveInput");
49
50    let item = encoding::decode::expand(input);
51    print("derive(Decode)", item.into_token_stream())
52}
53
54/// Derives `serde::Serialize` and `serde::Deserialize`.
55///
56/// # Struct attributes
57///
58/// `#[ast_serde("A")]` adds `"type": "A"` to json when serialized, and
59/// deserializes as the type only if `type` field of json string is `A`.
60///
61/// # Enum attributes
62///
63/// ## Type-level attributes
64///
65/// This macro does not accept arguments if used on enum.
66///
67/// ## Variant attributes
68///
69/// ### `#[tag("Expr")]`
70///
71/// You can tell "Use this variant if `type` is `Expr`".
72///
73/// This attribute can be applied multiple time, if a variant consumes multiple
74/// `type`s.
75///
76/// For example, `Lit` of swc_ecma_ast is an enum, but `Expr`, which contains
77/// `Lit` as a variant, is also an enum.
78/// So the `Lit` variant has multiple `#[tag]`-s like
79///
80/// ```rust,ignore
81/// enum Expr {
82///   #[tag("StringLiteral")]
83///   #[tag("NumericLiteral")]
84///   #[tag("BooleanLiteral")]
85///   Lit(Lit),
86/// }
87/// ```
88///
89/// so the deserializer can decide which variant to use.
90///
91///
92/// `#[tag]` also supports wildcard like `#[tag("*")]`. You can use this if
93/// there are two many variants.
94#[proc_macro_attribute]
95pub fn ast_serde(
96    args: proc_macro::TokenStream,
97    input: proc_macro::TokenStream,
98) -> proc_macro::TokenStream {
99    let input: DeriveInput = parse(input).expect("failed to parse input as a DeriveInput");
100
101    // we should use call_site
102    let mut item = TokenStream::new();
103    match input.data {
104        Data::Enum(..) => {
105            if !args.is_empty() {
106                panic!("#[ast_serde] on enum does not accept any argument")
107            }
108
109            item.extend(quote!(
110                #[derive(::serde::Serialize, ::swc_common::DeserializeEnum)]
111                #[serde(untagged)]
112                #input
113            ));
114        }
115        _ => {
116            let args: Option<ast_node_macro::Args> = if args.is_empty() {
117                None
118            } else {
119                Some(parse(args).expect("failed to parse args of #[ast_serde]"))
120            };
121
122            let serde_tag = match input.data {
123                Data::Struct(DataStruct {
124                    fields: Fields::Named(..),
125                    ..
126                }) => {
127                    if args.is_some() {
128                        Some(quote!(#[serde(tag = "type")]))
129                    } else {
130                        None
131                    }
132                }
133                _ => None,
134            };
135
136            let serde_rename = args.as_ref().map(|args| {
137                let name = &args.ty;
138                quote!(#[serde(rename = #name)])
139            });
140
141            item.extend(quote!(
142                #[derive(::serde::Serialize, ::serde::Deserialize)]
143                #serde_tag
144                #[serde(rename_all = "camelCase")]
145                #serde_rename
146                #input
147            ));
148        }
149    };
150
151    print("ast_serde", item)
152}
153
154/// Adds the standard AST node derives and serialization metadata.
155///
156/// Structs derive `PartialEq` by default. Pass `no_partial_eq` after the node
157/// tag when the type provides a manual implementation:
158/// `#[ast_node("CustomNode", no_partial_eq)]`.
159#[proc_macro_attribute]
160pub fn ast_node(
161    args: proc_macro::TokenStream,
162    input: proc_macro::TokenStream,
163) -> proc_macro::TokenStream {
164    let mut input: DeriveInput = parse(input).expect("failed to parse input as a DeriveInput");
165
166    // we should use call_site
167    let mut item = TokenStream::new();
168    match &input.data {
169        Data::Enum(data) => {
170            use syn::parse::Parser;
171
172            let attrs = <syn::punctuated::Punctuated<syn::Ident, syn::Token![,]>>::parse_terminated
173                .parse(args)
174                .expect("failed to parse #[ast_node]");
175
176            let mut has_no_clone = false;
177            let mut has_no_unknown = false;
178            for attr in &attrs {
179                if attr == "no_clone" {
180                    has_no_clone = true;
181                } else if attr == "no_unknown" {
182                    has_no_unknown = true;
183                } else {
184                    panic!("unknown attribute: {attr:?}")
185                }
186            }
187
188            let clone = if !has_no_clone {
189                Some(quote!(#[derive(Clone)]))
190            } else {
191                None
192            };
193            let non_exhaustive = if !has_no_unknown {
194                Some(quote!(#[cfg_attr(swc_ast_unknown, non_exhaustive)]))
195            } else {
196                None
197            };
198
199            let mut data = data.clone();
200            if !has_no_unknown {
201                let unknown: syn::Variant = if data
202                    .variants
203                    .iter()
204                    .all(|variant| variant.fields.is_empty())
205                {
206                    syn::parse_quote! {
207                        #[cfg(all(swc_ast_unknown, feature = "encoding-impl"))]
208                        #[from_variant(ignore)]
209                        #[span(unknown)]
210                        #[encoding(unknown)]
211                        Unknown(u32)
212                    }
213                } else {
214                    syn::parse_quote! {
215                        #[cfg(all(swc_ast_unknown, feature = "encoding-impl"))]
216                        #[from_variant(ignore)]
217                        #[span(unknown)]
218                        #[encoding(unknown)]
219                        Unknown(u32, swc_common::unknown::Unknown)
220                    }
221                };
222
223                // insert unknown member
224                data.variants.insert(0, unknown);
225                input.data = Data::Enum(data);
226            }
227
228            item.extend(quote!(
229                #[allow(clippy::derive_partial_eq_without_eq)]
230                #[cfg_attr(
231                    feature = "serde-impl",
232                    derive(
233                        ::serde::Serialize,
234                    )
235                )]
236                #[derive(
237                    ::swc_common::FromVariant,
238                    ::swc_common::Spanned,
239                    Debug,
240                    PartialEq,
241                    ::swc_common::DeserializeEnum,
242                )]
243                #clone
244                #non_exhaustive
245                #[cfg_attr(
246                    feature = "serde-impl",
247                    serde(untagged)
248                )]
249                #[cfg_attr(
250                    feature = "encoding-impl",
251                    derive(::swc_common::Encode, ::swc_common::Decode)
252                )]
253                #input
254            ));
255        }
256        _ => {
257            let args: Option<ast_node_macro::Args> = if args.is_empty() {
258                None
259            } else {
260                Some(parse(args).expect("failed to parse args of #[ast_node]"))
261            };
262
263            let serde_tag = match input.data {
264                Data::Struct(DataStruct {
265                    fields: Fields::Named(..),
266                    ..
267                }) => {
268                    if args.is_some() {
269                        Some(quote!(#[cfg_attr(
270                            feature = "serde-impl",
271                            serde(tag = "type")
272                        )]))
273                    } else {
274                        None
275                    }
276                }
277                _ => None,
278            };
279
280            let serde_rename = args.as_ref().map(|args| {
281                let name = &args.ty;
282
283                quote!(#[cfg_attr(
284                    feature = "serde-impl",
285                    serde(rename = #name)
286                )])
287            });
288
289            let ast_node_impl = args
290                .as_ref()
291                .map(|args| ast_node_macro::expand_struct(args.clone(), input.clone()));
292
293            let partial_eq = match &args {
294                Some(args) if args.no_partial_eq => None,
295                _ => Some(quote!(PartialEq,)),
296            };
297
298            item.extend(quote!(
299                #[allow(clippy::derive_partial_eq_without_eq)]
300                #[derive(::swc_common::Spanned, Clone, Debug, #partial_eq)]
301                #[cfg_attr(
302                    feature = "serde-impl",
303                    derive(::serde::Serialize, ::serde::Deserialize)
304                )]
305                #serde_tag
306                #[cfg_attr(
307                    feature = "serde-impl",
308                    serde(rename_all = "camelCase")
309                )]
310                #serde_rename
311                #[cfg_attr(
312                    feature = "encoding-impl",
313                    derive(::swc_common::Encode, ::swc_common::Decode)
314                )]
315                #input
316            ));
317
318            if let Some(items) = ast_node_impl {
319                for item_impl in items {
320                    item.extend(item_impl.into_token_stream());
321                }
322            }
323        }
324    };
325
326    print("ast_node", item)
327}