cheers-ast 0.1.0-alpha.1

Internal AST support crate for cheers.
Documentation
use proc_macro2::TokenStream;
use quote::{ToTokens, quote, quote_spanned};
use syn::{
    Ident, LitBool, LitChar, LitFloat, LitInt, LitStr, Token,
    ext::IdentExt,
    parse::{Parse, ParseStream},
    spanned::Spanned,
    token::{Brace, Bracket, Paren},
};

use super::{ElementBody, Generate, Generator, Literal, ParenExpr, ParenExprMode};
use crate::{AttributeValueNode, Context, SyntaxStatic};

pub struct Component {
    pub name: Ident,
    pub attrs: Vec<ComponentAttribute>,
    pub default_attrs: Option<ComponentDefaultAttributes>,
    pub dotdot: Option<Token![..]>,
    pub body: ElementBody,
}

impl SyntaxStatic for Component {
    fn is_static(&self) -> bool {
        false
    }
}

impl Component {
    fn children_lazy(&mut self, g: &mut Generator<'_>) -> Option<TokenStream> {
        match &mut self.body {
            ElementBody::Normal { children, .. } => {
                let buffer_ident = Generator::buffer_ident();

                let block = g.block_with(
                    Brace::default(),
                    |g| {
                        g.push(children);
                    },
                    true,
                );

                Some(quote! {
                    ::cheers::prelude::Lazy::dangerously_create(
                        |#buffer_ident: &mut ::cheers::prelude::Buffer|
                            #block
                    )
                })
            }
            ElementBody::Void { .. } => None,
        }
    }

    fn default_setters(&self, g: &mut Generator<'_>) -> Vec<TokenStream> {
        let mut setters = Vec::new();

        if let Some(default_attrs) = &self.default_attrs {
            for attr in &default_attrs.attrs {
                let name = &attr.name;
                let value = attr.value_expr(g);

                setters.push(quote!(.#name(#value)));
            }
        }

        setters
    }

    fn required_attrs_in_signature_order(&self) -> Vec<&ComponentAttribute> {
        let mut attrs = self.attrs.iter().collect::<Vec<_>>();
        attrs.sort_by_key(|attr| attr.name.unraw().to_string());
        attrs
    }

    fn build_suffix(children_lazy: Option<TokenStream>) -> TokenStream {
        match children_lazy {
            Some(children_lazy) => quote!(.__cheers_build_with_children(#children_lazy)),
            None => quote!(.__cheers_build()),
        }
    }

    fn generate_dotdot_tokens(&mut self, g: &mut Generator<'_>) -> TokenStream {
        let fields = self
            .attrs
            .iter()
            .map(|attr| {
                let name = &attr.name;
                let value = attr.value_expr(g);

                quote!(#name: #value,)
            })
            .collect::<Vec<_>>();

        let children = self.children_lazy(g).map(|children| {
            let children_ident = Ident::new("children", self.name.span());
            quote!(#children_ident: #children,)
        });

        let name = &self.name;
        let default = self
            .dotdot
            .as_ref()
            .map(|dotdot| quote_spanned!(dotdot.span()=> ..::core::default::Default::default()))
            .unwrap_or_default();

        quote! {
            #name {
                #(#fields)*
                #children
                #default
            }
        }
    }

    fn generate_prop_builder_tokens(&mut self, g: &mut Generator<'_>) -> TokenStream {
        let required_attrs = self
            .required_attrs_in_signature_order()
            .into_iter()
            .map(|attr| {
                let field = attr.name.clone();
                let method = Ident::new(
                    &format!("__cheers_prop_{}", attr.name.unraw()),
                    attr.name.span(),
                );
                let value = attr.value_expr(g);

                (field, method, value)
            });

        let required_attrs = required_attrs.collect::<Vec<_>>();
        let default_setters = self.default_setters(g);
        let build_suffix = Self::build_suffix(self.children_lazy(g));

        let name = &self.name;
        let runtime_required_constructors = required_attrs
            .iter()
            .map(|(_, method, value)| quote!(#name::#method(#value)));

        let required_assignments = required_attrs.iter().map(|(field, _, value)| {
            quote! {
                __cheers_required.#field = #value;
            }
        });

        let runtime_constructor = quote! {
            #name::__cheers_props(#(#runtime_required_constructors),*)
            #(#default_setters)*
        };

        let ra_constructor = quote! {
            {
                let mut __cheers_required = #name::__cheers_required();
                #(#required_assignments)*
                #name::__cheers_props_from_required(__cheers_required)
                #(#default_setters)*
            }
        };

        quote! {
            {
                #[allow(unexpected_cfgs, unused_parens)]
                let __cheers_component = {
                    #[cfg(rust_analyzer)]
                    {
                        #ra_constructor
                        #build_suffix
                    }

                    #[cfg(not(rust_analyzer))]
                    {
                        #runtime_constructor
                        #build_suffix
                    }
                };

                __cheers_component
            }
        }
    }
}

impl Generate for Component {
    const CONTEXT: Context = Context::Element;

    fn generate(&mut self, g: &mut Generator<'_>) {
        let tokens = if self.default_attrs.is_some() && self.dotdot.is_none() {
            self.generate_prop_builder_tokens(g)
        } else {
            self.generate_dotdot_tokens(g)
        };
        g.push_expr(Paren::default(), Self::CONTEXT, tokens);
    }
}

pub struct ComponentDefaultAttributes {
    pub bracket_token: Bracket,
    pub attrs: Vec<ComponentAttribute>,
}

impl Parse for ComponentDefaultAttributes {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let content;

        Ok(Self {
            bracket_token: syn::bracketed!(content in input),
            attrs: {
                let mut attrs = Vec::new();

                while !content.is_empty() {
                    attrs.push(content.parse()?);
                }

                attrs
            },
        })
    }
}

pub struct ComponentAttribute {
    pub name: Ident,
    pub value: Option<ComponentAttributeValue>,
}

impl ComponentAttribute {
    pub(crate) fn value_expr(&self, g: &mut Generator<'_>) -> TokenStream {
        match &self.value {
            Some(ComponentAttributeValue::Literal(lit)) => lit.to_token_stream(),
            Some(ComponentAttributeValue::Expr(expr)) => match expr.mode {
                ParenExprMode::Normal => {
                    let mut tokens = TokenStream::new();

                    expr.paren_token.surround(&mut tokens, |tokens| {
                        expr.body.to_tokens(tokens);
                    });

                    tokens
                }
                ParenExprMode::Ref => g
                    .hoist_ref_expr(expr.paren_token, &expr.body)
                    .to_token_stream(),
            },
            Some(ComponentAttributeValue::Ident(ident)) => ident.to_token_stream(),
            None => self.name.to_token_stream(),
        }
    }
}

impl Parse for ComponentAttribute {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let name = input.parse()?;
        Ok(Self {
            name,
            value: {
                if input.peek(Token![=]) {
                    input.parse::<Token![=]>()?;
                    Some(input.parse()?)
                } else {
                    None
                }
            },
        })
    }
}

#[allow(clippy::large_enum_variant)]
pub enum ComponentAttributeValue {
    Literal(Literal),
    Ident(Ident),
    Expr(ParenExpr<AttributeValueNode>),
}

impl Parse for ComponentAttributeValue {
    fn parse(input: ParseStream) -> syn::Result<Self> {
        let lookahead = input.lookahead1();

        if lookahead.peek(LitStr)
            || lookahead.peek(LitInt)
            || lookahead.peek(LitBool)
            || lookahead.peek(LitFloat)
            || lookahead.peek(LitChar)
        {
            input.call(Literal::parse_any).map(Self::Literal)
        } else if lookahead.peek(Ident) {
            input.parse().map(Self::Ident)
        } else if lookahead.peek(Paren) {
            input.parse().map(Self::Expr)
        } else {
            Err(lookahead.error())
        }
    }
}