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