1use proc_macro2::TokenStream;
2use quote::{quote, quote_spanned, ToTokens};
3use syn::parse::{Parse, ParseStream};
4use syn::punctuated::Punctuated;
5use syn::spanned::Spanned;
6use syn::{Expr, ExprLit, Lit, LitStr, Token};
7
8pub struct Classes(Punctuated<ClassExpr, Token![,]>);
10
11impl Parse for Classes {
12 fn parse(input: ParseStream) -> syn::Result<Self> {
13 input
14 .parse_terminated(ClassExpr::parse, Token![,])
15 .map(Self)
16 }
17}
18
19impl ToTokens for Classes {
20 fn to_tokens(&self, tokens: &mut TokenStream) {
21 let n = self.0.len();
22 let push_classes = self.0.iter().map(|x| match x {
23 ClassExpr::Lit(class) => quote! {
24 unsafe { __yew_classes.unchecked_push(#class) };
25 },
26 ClassExpr::Expr(class) => quote_spanned! {class.span()=>
27 __yew_classes.push(#class);
28 },
29 });
30 tokens.extend(quote! {
31 {
32 let mut __yew_classes = ::yew::html::Classes::with_capacity(#n);
33 #(#push_classes)*
34 __yew_classes
35 }
36 });
37 }
38}
39
40enum ClassExpr {
41 Lit(LitStr),
42 Expr(Box<Expr>),
43}
44
45impl Parse for ClassExpr {
46 fn parse(input: ParseStream) -> syn::Result<Self> {
47 match input.parse()? {
48 Expr::Lit(ExprLit {
49 lit: Lit::Str(lit_str),
50 ..
51 }) => {
52 let value = lit_str.value();
53 let classes = value.split_whitespace().collect::<Vec<_>>();
54 if classes.len() > 1 {
55 let fix = classes
56 .into_iter()
57 .map(|class| format!("\"{class}\""))
58 .collect::<Vec<_>>()
59 .join(", ");
60 let msg = format!(
61 "string literals must not contain more than one class (hint: use `{fix}`)"
62 );
63
64 Err(syn::Error::new(lit_str.span(), msg))
65 } else {
66 Ok(Self::Lit(lit_str))
67 }
68 }
69 expr => Ok(Self::Expr(Box::new(expr))),
70 }
71 }
72}