use proc_macro::TokenStream;
use quote::{ToTokens, quote};
use syn::{FnArg, Ident, Pat, spanned::Spanned};
pub(crate) fn component_inner(_attrs: TokenStream, input: TokenStream) -> TokenStream {
let ast = syn::parse_macro_input!(input as syn::ItemFn);
let name = &ast.sig.ident;
let fn_attrs = &ast.attrs;
let (impl_generics, ty_generics, where_clause) = &ast.sig.generics.split_for_impl();
if ast.sig.output.to_token_stream().to_string() != "" {
emit_error!(
ast.sig.output.span(),
"{} => \"{}\"",
"Remove the information about the returned type. A component always returns DomNode",
ast.sig.output.to_token_stream().to_string()
);
return quote! {}.into();
}
let mut struct_fields = Vec::new();
let mut struct_assignments = Vec::new();
let mut group_fields = Vec::new();
let mut group_assigments = Vec::new();
let mut group_methods = Vec::new();
for field in ast.sig.inputs.clone().into_iter() {
match field {
FnArg::Receiver(_) => unreachable!(),
FnArg::Typed(mut pat_type) => {
let attrs = pat_type.attrs.drain(..);
let entry = match pat_type.pat.as_mut() {
Pat::Ident(ident) => {
ident.mutability = None;
ident.clone()
}
_ => {
emit_warning!(pat_type.pat.span(), "Expected ident");
continue;
}
};
let name = entry.ident.clone();
let push_method_name = get_group_attrs_push_method_name(&name);
let replace_method_name = get_group_attrs_replace_method_name(&name);
if pat_type.ty.to_token_stream().to_string() == "AttrGroup" {
group_fields.push(quote! {
#(#attrs)* pub #pat_type
});
group_assigments.push(quote! {
#name: Default::default()
});
group_methods.push(quote! {
#[doc="Push single attribute to AttrGroup"]
pub fn #push_method_name(mut self, key: String, value: vertigo::AttrGroupValue) -> Self {
self.#name.insert(key, value);
self
}
#[doc="Replace AttrGroup with new AttrGroup (f. ex. upon spreading into component)"]
pub fn #replace_method_name(mut self, group: vertigo::AttrGroup) -> Self {
self.#name = group;
self
}
});
} else {
struct_fields.push(quote! {
#(#attrs)* pub #pat_type
});
struct_assignments.push(quote! {
#name: self.#name
});
}
}
}
}
let body = ast.block;
let mut param_assignments = Vec::new();
for param in &ast.sig.inputs {
if let syn::FnArg::Typed(pat_type) = param
&& let syn::Pat::Ident(ident) = &*pat_type.pat
{
let param_name = ident.ident.clone();
let mutability = ident.mutability;
param_assignments.push(quote! {
let #mutability #param_name = self.#param_name;
});
}
}
let visibility = &ast.vis;
let component_name = get_component_name(&name);
let result = quote! {
#(#fn_attrs)*
#visibility struct #name #impl_generics #where_clause {
#(#struct_fields,)*
}
#visibility struct #component_name #impl_generics #where_clause {
#(#struct_fields,)*
#(#group_fields,)*
}
impl #impl_generics #name #ty_generics #where_clause {
pub fn into_component(self) -> #component_name #ty_generics {
#component_name {
#(#struct_assignments,)*
#(#group_assigments,)*
}
}
#[doc="Shorthand for `.into_component().mount()` - bypasses setting of dynamic attributes (AttrGroup)."]
pub fn mount(self) -> vertigo::DomNode {
self.into_component().mount()
}
}
impl #impl_generics #component_name #ty_generics #where_clause {
#(#group_methods)*
pub fn mount(self) -> vertigo::DomNode {
#(#param_assignments)*
(#body).into()
}
}
};
result.into()
}
pub fn get_component_name<T: ToTokens + Spanned>(constructor_name: &T) -> Ident {
Ident::new(
&format!("__{}Component", constructor_name.to_token_stream()),
constructor_name.span(),
)
}
pub fn get_group_attrs_push_method_name<T: ToTokens + Spanned>(group_name: &T) -> Ident {
Ident::new(
&format!("group_{}_push", group_name.to_token_stream()),
group_name.span(),
)
}
pub fn get_group_attrs_replace_method_name<T: ToTokens + Spanned>(group_name: &T) -> Ident {
Ident::new(
&format!("group_{}_replace", group_name.to_token_stream()),
group_name.span(),
)
}