Skip to main content

oxc_css_parser_macro/
lib.rs

1use proc_macro::TokenStream;
2use quote::quote;
3use syn::{Data, DataStruct, DeriveInput, Fields, Ident};
4
5#[proc_macro_derive(Spanned)]
6pub fn spanned_derive(input: TokenStream) -> TokenStream {
7    let ast: DeriveInput = syn::parse(input).unwrap();
8    let name = &ast.ident;
9    let generics = &ast.generics;
10
11    let generated = match &ast.data {
12        Data::Struct(..) => quote! {
13            impl #generics crate::pos::Spanned for #name #generics {
14                #[inline]
15                fn span(&self) -> &Span {
16                    &self.span
17                }
18            }
19        },
20        Data::Enum(data_enum) => {
21            let variants = data_enum.variants.iter().map(|variant| {
22                let ident = &variant.ident;
23                let fields = &variant.fields;
24                match fields {
25                    Fields::Unnamed(..) => quote! {
26                        Self::#ident(x) => x.span(),
27                    },
28                    Fields::Unit => quote! {
29                        Self::#ident => unimplemented!(),
30                    },
31                    _ => unimplemented!("enum variant with named fields is not supported"),
32                }
33            });
34            quote! {
35                impl #generics crate::pos::Spanned for #name #generics {
36                    fn span(&self) -> &Span {
37                        match self {
38                            #(#variants)*
39                        }
40                    }
41                }
42            }
43        }
44        _ => unimplemented!(),
45    };
46
47    generated.into()
48}
49
50#[proc_macro_derive(SpanIgnoredEq)]
51pub fn span_ignored_eq_derive(input: TokenStream) -> TokenStream {
52    let ast: DeriveInput = syn::parse(input).unwrap();
53    let name = &ast.ident;
54    let generics = &ast.generics;
55
56    let generated = match &ast.data {
57        Data::Struct(DataStruct {
58            fields: Fields::Named(fields),
59            ..
60        }) => {
61            let comparions = fields
62                .named
63                .iter()
64                .filter_map(|field| field.ident.as_ref())
65                .filter(|ident| *ident != "span")
66                .map(|ident| quote! { && self.#ident.span_ignored_eq(&other.#ident) });
67            quote! {
68                impl #generics crate::SpanIgnoredEq for #name #generics {
69                    #[must_use]
70                    fn span_ignored_eq(&self, other: &Self) -> bool {
71                        true #(#comparions)*
72                    }
73                }
74            }
75        }
76        Data::Enum(data_enum) => {
77            let variants = data_enum.variants.iter().map(|variant| {
78                let ident = &variant.ident;
79                let fields = &variant.fields;
80                match fields {
81                    Fields::Unnamed(..) => quote! {
82                        (Self::#ident(a), Self::#ident(b)) => a.span_ignored_eq(&b),
83                    },
84                    Fields::Unit => quote! {
85                        (Self::#ident, Self::#ident) => true,
86                    },
87                    _ => unimplemented!("enum variant with named fields is not supported"),
88                }
89            });
90            quote! {
91                impl #generics crate::SpanIgnoredEq for #name #generics {
92                    #[must_use]
93                    fn span_ignored_eq(&self, other: &Self) -> bool {
94                        match (self, other) {
95                            #(#variants)*
96                            _ => false,
97                        }
98                    }
99                }
100            }
101        }
102        _ => unimplemented!(),
103    };
104
105    generated.into()
106}
107
108#[proc_macro_derive(EnumAsIs)]
109pub fn enum_as_is_derive(input: TokenStream) -> TokenStream {
110    use heck::ToSnakeCase;
111
112    let ast: DeriveInput = syn::parse(input).unwrap();
113    let name = &ast.ident;
114    let generics = &ast.generics;
115
116    let generated = match &ast.data {
117        Data::Enum(data_enum) => {
118            let variants = data_enum.variants.iter().map(|variant| {
119                let original_name = &variant.ident;
120                let variant_name = original_name.to_string().to_snake_case();
121
122                let ident_is = Ident::new(&format!("is_{variant_name}"), variant.ident.span());
123                let ident_as = Ident::new(&format!("as_{variant_name}"), variant.ident.span());
124
125                let doc_is = format!("Checks inner field whether it's of variant [`{original_name}`](Self::{original_name}) or not.");
126                let doc_as = format!("Returns [`Some`] with a reference to inner field if it's of variant [`{original_name}`](Self::{original_name}), otherwise returns [`None`].");
127
128                match &variant.fields {
129                    Fields::Unnamed(unamed_fields) => {
130                        let ty = &unamed_fields.unnamed.first().unwrap().ty;
131                        quote! {
132                            #[doc = #doc_is]
133                            #[inline]
134                            pub fn #ident_is(&self) -> bool {
135                                match self {
136                                    Self::#original_name(..) => true,
137                                    _ => false
138                                }
139                            }
140                            #[doc = #doc_as]
141                            #[inline]
142                            pub fn #ident_as(&self) -> Option<&#ty> {
143                                match self {
144                                    Self::#original_name(value) => Some(value),
145                                    _ => None,
146                                }
147                            }
148                        }
149                    }
150                    Fields::Unit => quote! {
151                        #[doc = #doc_is]
152                        #[inline]
153                        pub fn #ident_is(&self) -> bool {
154                            match self {
155                                Self::#original_name => true,
156                                _ => false,
157                            }
158                        }
159                    },
160                    _ => unimplemented!("enum variant with named fields is not supported"),
161                }
162            });
163
164            quote! {
165                impl #generics #name #generics {
166                    #(#variants)*
167                }
168            }
169        }
170        _ => unimplemented!("only enum is supported"),
171    };
172
173    generated.into()
174}