frender-macros 1.0.0-alpha.8

macros for frender
Documentation
use darling::FromMeta;
use syn::{punctuated::Pair, spanned::Spanned};

use crate::err::{
    maybe_with_error, OptionCombineExt, OutputError, RecordError, ResultUnwrapValueAndErrorExt,
    ResultUnwrapValueExt,
};

#[derive(Debug, FromMeta)]
pub struct ComponentMainOptions {
    #[darling(default)]
    pub no_strict_mode: darling::util::Flag,
    pub mount_element_id: String,
}

#[derive(Debug, FromMeta, Default)]
pub struct ComponentOptions {
    #[darling(default)]
    pub display_name: Option<String>,
    #[darling(default)]
    pub no_debug_props: darling::util::Flag,
    #[darling(default)]
    pub main: Option<darling::util::SpannedValue<ComponentMainOptions>>,
}

pub struct ComponentDefinition {
    pub errors: Vec<darling::Error>,
    pub options: ComponentOptions,
    pub item_fn: ComponentItemFn,
}

impl ComponentDefinition {
    pub fn from_attrs_and_fn(attr_args: syn::AttributeArgs, input_fn: syn::ItemFn) -> Self {
        let mut errors = vec![];
        let options = match ComponentOptions::from_list(&attr_args) {
            Ok(v) => v,
            Err(err) => {
                errors.push(err);
                ComponentOptions::default()
            }
        };

        let item_fn = ComponentItemFn::try_from_input_fn(input_fn)
            .unwrap_value_and_record_error(&mut errors)
            .unwrap_value();

        Self {
            options,
            item_fn,
            errors,
        }
    }
}

pub struct ComponentItemFn {
    pub attrs: Vec<syn::Attribute>,
    pub vis: syn::Visibility,
    pub sig: ComponentItemFnSignature,
    pub block: Box<syn::Block>,
}

pub struct ComponentItemFnSignature {
    pub fn_token: syn::Token![fn],
    pub ident: syn::Ident,
    pub generics: syn::Generics,
    pub paren_token: syn::token::Paren,
    pub props_arg: Option<Pair<syn::PatType, syn::Token![,]>>,
    pub output: syn::ReturnType,
}

impl ComponentItemFn {
    pub fn try_from_input_fn(input_fn: syn::ItemFn) -> Result<Self, (Self, syn::Error)> {
        let syn::ItemFn {
            vis,
            sig,
            block,
            attrs,
            ..
        } = input_fn;

        let mut error = None;

        let sig = ComponentItemFnSignature::try_from_sig(sig)
            .unwrap_value_and_record_error(&mut error)
            .unwrap_value();

        let ret = Self {
            attrs,
            vis,
            sig,
            block,
        };

        error.into_value_result(ret)
    }
}

macro_rules! check_sig_field_is_none {
    ($error:ident $msg:literal  = $sig:ident . $field:ident) => {
        if let Some($field) = &$sig.$field {
            $crate::err::OptionCombineExt::combine_or_replace(
                $error,
                syn::Error::new(
                    $field.span(),
                    concat!("frender component doesn't support `", $msg, "` fn"),
                ),
            );
        }
    };
}

impl ComponentItemFnSignature {
    pub fn try_from_sig(sig: syn::Signature) -> Result<Self, (Self, syn::Error)> {
        let mut error = None;

        let error_mut = &mut error;
        check_sig_field_is_none!(error_mut "const" = sig.constness);
        check_sig_field_is_none!(error_mut "async" = sig.asyncness);
        check_sig_field_is_none!(error_mut "unsafe" = sig.unsafety);
        check_sig_field_is_none!(error_mut "extern" = sig.abi);
        check_sig_field_is_none!(error_mut "variadic" = sig.variadic);

        let syn::Signature {
            mut generics,
            ident,
            output,
            mut inputs,
            fn_token,
            paren_token,
            ..
        } = sig;

        if generics.params.first().map_or(false, |tp| match tp {
            syn::GenericParam::Lifetime(_) => true,
            _ => false,
        }) {
            generics.params = generics
                .params
                .into_pairs()
                .filter(|p| match p.value() {
                    syn::GenericParam::Lifetime(lt) => {
                        error.record_error(syn::Error::new(
                            lt.span(),
                            "frender component currently does not support lifetimes",
                        ));
                        false
                    }
                    _ => true,
                })
                .collect();
        }

        let arg_props = inputs.pop();

        for other_arg in inputs {
            error.combine_or_replace(syn::Error::new(
                other_arg.span(),
                "frender component fn expects at most one argument as props",
            ));
        }

        let props_arg = if let Some((arg_props, comma)) = arg_props.map(Pair::into_tuple) {
            match arg_props {
                syn::FnArg::Receiver(v) => {
                    error.combine_or_replace(syn::Error::new(
                        v.span(),
                        "`self` argument is not allowed in frender component fn",
                    ));
                    None
                }
                syn::FnArg::Typed(v) => {
                    for attr in &v.attrs {
                        error.combine_or_replace(syn::Error::new(
                            attr.span(),
                            "attributes are not allowed for arguments of frender component fn",
                        ));
                    }
                    Some(Pair::new(v, comma))
                }
            }
        } else {
            None
        };

        let sig = Self {
            fn_token,
            ident,
            generics,
            paren_token,
            props_arg,
            output,
        };

        maybe_with_error(sig, error)
    }
}