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            use syn::parse::Parser;
162
163            let attrs = <syn::punctuated::Punctuated<syn::Ident, syn::Token![,]>>::parse_terminated
164                .parse(args)
165                .expect("failed to parse #[ast_node]");
166
167            let mut has_no_clone = false;
168            let mut has_no_unknown = false;
169            for attr in &attrs {
170                if attr == "no_clone" {
171                    has_no_clone = true;
172                } else if attr == "no_unknown" {
173                    has_no_unknown = true;
174                } else {
175                    panic!("unknown attribute: {attr:?}")
176                }
177            }
178
179            let clone = if !has_no_clone {
180                Some(quote!(#[derive(Clone)]))
181            } else {
182                None
183            };
184            let non_exhaustive = if !has_no_unknown {
185                Some(quote!(#[cfg_attr(swc_ast_unknown, non_exhaustive)]))
186            } else {
187                None
188            };
189
190            item.extend(quote!(
191                #[allow(clippy::derive_partial_eq_without_eq)]
192                #[cfg_attr(
193                    feature = "serde-impl",
194                    derive(
195                        ::serde::Serialize,
196                    )
197                )]
198                #[derive(
199                    ::swc_common::FromVariant,
200                    ::swc_common::Spanned,
201                    Debug,
202                    PartialEq,
203                    ::swc_common::DeserializeEnum,
204                )]
205                #clone
206                #non_exhaustive
207                #[cfg_attr(
208                    feature = "rkyv-impl",
209                    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
210                )]
211                #[cfg_attr(
212                    feature = "rkyv-impl",
213                    rkyv(deserialize_bounds(__D::Error: rkyv::rancor::Source))
214                )]
215                #[cfg_attr(feature = "rkyv-impl", repr(u32))]
216                #[cfg_attr(
217                    feature = "rkyv-impl",
218                    rkyv(serialize_bounds(__S: rkyv::ser::Writer + rkyv::ser::Allocator,
219                        __S::Error: rkyv::rancor::Source))
220                )]
221                #[cfg_attr(
222                    feature = "rkyv-impl",
223                    rkyv(bytecheck(bounds(
224                        __C: rkyv::validation::ArchiveContext,
225                        __C::Error: rkyv::rancor::Source
226                    )))
227                )]
228                #[cfg_attr(
229                    feature = "serde-impl",
230                    serde(untagged)
231                )]
232                #input
233            ));
234        }
235        _ => {
236            let args: Option<ast_node_macro::Args> = if args.is_empty() {
237                None
238            } else {
239                Some(parse(args).expect("failed to parse args of #[ast_node]"))
240            };
241
242            let serde_tag = match input.data {
243                Data::Struct(DataStruct {
244                    fields: Fields::Named(..),
245                    ..
246                }) => {
247                    if args.is_some() {
248                        Some(quote!(#[cfg_attr(
249                            feature = "serde-impl",
250                            serde(tag = "type")
251                        )]))
252                    } else {
253                        None
254                    }
255                }
256                _ => None,
257            };
258
259            let serde_rename = args.as_ref().map(|args| {
260                let name = &args.ty;
261
262                quote!(#[cfg_attr(
263                    feature = "serde-impl",
264                    serde(rename = #name)
265                )])
266            });
267
268            let ast_node_impl = args
269                .as_ref()
270                .map(|args| ast_node_macro::expand_struct(args.clone(), input.clone()));
271
272            item.extend(quote!(
273                #[allow(clippy::derive_partial_eq_without_eq)]
274                #[derive(::swc_common::Spanned, Clone, Debug, PartialEq)]
275                #[cfg_attr(
276                    feature = "serde-impl",
277                    derive(::serde::Serialize, ::serde::Deserialize)
278                )]
279                #[cfg_attr(
280                    feature = "rkyv-impl",
281                    derive(rkyv::Archive, rkyv::Serialize, rkyv::Deserialize)
282                )]
283                #[cfg_attr(
284                    feature = "rkyv-impl",
285                    rkyv(deserialize_bounds(__D::Error: rkyv::rancor::Source))
286                )]
287                #[cfg_attr(
288                    feature = "rkyv-impl",
289                    rkyv(bytecheck(bounds(
290                        __C: rkyv::validation::ArchiveContext,
291                        __C::Error: rkyv::rancor::Source
292                    )))
293                )]
294                #[cfg_attr(feature = "rkyv-impl", repr(C))]
295                #[cfg_attr(
296                    feature = "rkyv-impl",
297                    rkyv(serialize_bounds(__S: rkyv::ser::Writer + rkyv::ser::Allocator,
298                        __S::Error: rkyv::rancor::Source))
299                )]
300                #serde_tag
301                #[cfg_attr(
302                    feature = "serde-impl",
303                    serde(rename_all = "camelCase")
304                )]
305                #serde_rename
306                #input
307            ));
308
309            if let Some(items) = ast_node_impl {
310                for item_impl in items {
311                    item.extend(item_impl.into_token_stream());
312                }
313            }
314        }
315    };
316
317    print("ast_node", item)
318}