Skip to main content
  1. Today I Learned/

TIL: Preventing Inline Styles at Compile Time with Rust Proc-Macros

1 min

What I Learned #

You can create a custom proc-macro in Rust that wraps Maud’s html! macro to prevent inline style attributes at compile time, enforcing the use of CSS classes instead.

Example #

// oxidesk-macros/src/lib.rs
use proc_macro::TokenStream;
use proc_macro2::TokenTree;
use quote::quote;

#[proc_macro]
pub fn html(input: TokenStream) -> TokenStream {
    let input2: proc_macro2::TokenStream = input.clone().into();
    let tokens: Vec<TokenTree> = input2.into_iter().collect();

    // Check for style= attribute
    for window in tokens.windows(2) {
        if let (TokenTree::Ident(ident), TokenTree::Punct(punct)) = (&window[0], &window[1]) {
            if ident.to_string() == "style" && punct.as_char() == '=' {
                return syn::Error::new(ident.span(),
                    "inline `style` attribute is not allowed; use CSS classes instead")
                    .to_compile_error()
                    .into();
            }
        }
    }

    // Delegate to maud
    let input3: proc_macro2::TokenStream = input.into();
    let expanded = quote! {
        ::maud::html! { #input3 }
    };
    expanded.into()
}

Why It’s Useful #

This prevents inline styles from creeping into your codebase, maintaining a consistent design system through CSS classes. The compiler catches violations before the code even runs. For edge cases like email templates, you can provide an html_unchecked! escape hatch.

Reference #