pinkie_macros/
lib.rs

1use std::borrow::Cow;
2
3#[cfg(feature = "validation")]
4use lightningcss::stylesheet::{ParserOptions, StyleSheet};
5use quote::quote;
6use xxhash_rust::xxh64::xxh64;
7
8#[proc_macro]
9pub fn css(input: proc_macro::TokenStream) -> proc_macro::TokenStream {
10    let data = pinkie_parser::parse(input.into());
11    let css = data.css;
12
13    #[cfg(feature = "validation")]
14    if let Err(e) = StyleSheet::parse(&format!(".dummy{{{css}}}"), ParserOptions::default()) {
15        let err = format!("css error: {}", e.kind);
16        if let Some(loc) = e.loc {
17            let offset = loc.column as usize - 8; // account for `.dummy{` and columns being 1-based
18
19            // println!("{css}");
20            // let mut prev = 0;
21            // let mut highlighted = false;
22            // for (pos, _) in &data.spans {
23            //     if *pos >= offset && !highlighted {
24            //         highlighted = true;
25            //         print!("{: >fill$}\x1b[1;31m*\x1b[0m", "", fill = pos - prev);
26            //     } else {
27            //         print!("{: >fill$}^", "", fill = pos - prev);
28            //     }
29            //     prev = *pos + 1; // account for ^ taking up a space
30            // }
31            // println!();
32
33            let span = data
34                .spans
35                .into_iter()
36                .find_map(|(pos, span)| (offset <= pos).then_some(span));
37            if let Some(span) = span {
38                return quote::quote_spanned!(span => compile_error!(#err)).into();
39            }
40        }
41        return quote!(compile_error!(#err)).into();
42    };
43
44    let prefix = std::env::var("PINKIE_CSS_CLASS_PREFIX")
45        .map(Cow::Owned)
46        .unwrap_or("pinkie-".into());
47
48    let hash = &format!("{:08x}", xxh64(css.as_bytes(), 0))[0..8];
49    let class = format!("{prefix}{hash}");
50
51    let (line, location) = if cfg!(feature = "location") {
52        (
53            quote!(
54                const LINE: usize = line!() as usize;
55            ),
56            quote! {
57                location: ::pinkie::Location {
58                    file: file!(),
59                    line: LINE,
60                },
61            },
62        )
63    } else {
64        (quote!(), quote!())
65    };
66
67    quote! {{ #line;
68        const STYLE: ::pinkie::Style = ::pinkie::Style {
69            class: #class,
70            css: #css,
71            #location
72        };
73        ::pinkie::__submit!(STYLE);
74        STYLE
75    }}
76    .into()
77}