use proc_macro2::TokenStream;
use quote::quote;
use syn::{Generics, ItemFn};
use crate::component::error::{ComponentError, ComponentResult};
use crate::component::types::{
CodeGenConfig, ComponentInfo, ComponentType, GeneratedComponentMetadata,
};
pub struct ComponentCodeGenerator {
config: CodeGenConfig,
}
impl ComponentCodeGenerator {
pub fn new() -> Self {
Self {
config: CodeGenConfig::default(),
}
}
pub fn with_config(config: CodeGenConfig) -> Self {
Self { config }
}
pub fn generate(
&self,
input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
match &component_info.component_type {
ComponentType::PropsBased { .. } => {
self.generate_props_based_component(input, component_info)
}
ComponentType::DirectParams { .. } => {
self.generate_direct_params_component(input, component_info)
}
ComponentType::NoParams => self.generate_no_params_component(input, component_info),
}
}
fn generate_props_based_component(
&self,
input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
let generator = PropBasedGenerator::new(&self.config);
let code = generator.generate(input, component_info)?;
let metadata = GeneratedComponentMetadata::props_based(
component_info.name.clone(),
component_info.props_struct_name(),
component_info.component_struct_name(),
);
if self.config.generate_docs {
let docs = common::generate_docs(component_info, &metadata);
let param_docs = if component_info.has_parameters() {
quote! {
#[doc = "This component accepts parameters."]
}
} else {
quote! {
#[doc = "This component does not accept parameters."]
}
};
Ok(quote! {
#docs
#param_docs
#code
})
} else {
Ok(code)
}
}
fn generate_direct_params_component(
&self,
input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
let generator = DirectParamsGenerator::new(&self.config);
let code = generator.generate(input, component_info)?;
let parameter_names = if let Some(params) = component_info.direct_parameters() {
params.iter().map(|p| p.name.to_string()).collect()
} else {
Vec::new()
};
let metadata = GeneratedComponentMetadata::direct_params(
component_info.name.clone(),
component_info.props_struct_name(),
component_info.component_struct_name(),
parameter_names,
);
if self.config.generate_docs {
let docs = common::generate_docs(component_info, &metadata);
Ok(quote! {
#docs
#code
})
} else {
Ok(code)
}
}
fn generate_no_params_component(
&self,
input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
let generator = NoParamsGenerator::new(&self.config);
let code = generator.generate(input, component_info)?;
let metadata = GeneratedComponentMetadata::no_params(
component_info.name.clone(),
component_info.props_struct_name(),
component_info.component_struct_name(),
);
if self.config.generate_docs {
let docs = common::generate_docs(component_info, &metadata);
Ok(quote! {
#docs
#code
})
} else {
Ok(code)
}
}
}
impl Default for ComponentCodeGenerator {
fn default() -> Self {
Self::new()
}
}
trait ComponentGenerator {
fn generate(
&self,
input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream>;
}
pub mod common {
use super::*;
pub fn generate_component_struct_impl(
component_info: &ComponentInfo,
props_struct_name: &syn::Ident,
) -> TokenStream {
let component_struct_name = component_info.component_struct_name();
let fn_generics = &component_info.generics;
let (impl_generics, ty_generics, where_clause) = fn_generics.split_for_impl();
quote! {
impl #impl_generics Default for #component_struct_name #ty_generics #where_clause {
fn default() -> Self {
Self {
props: Default::default(),
}
}
}
impl #impl_generics #component_struct_name #ty_generics #where_clause {
pub fn new(props: #props_struct_name) -> Self {
Self { props }
}
pub fn with_children(mut self, children: Vec<Element>) -> Self {
self.props.set_children(children);
self
}
}
}
}
pub fn generate_component_trait_impl(component_info: &ComponentInfo) -> TokenStream {
let component_struct_name = component_info.component_struct_name();
let fn_name = &component_info.name;
let fn_generics = &component_info.generics;
let (impl_generics, ty_generics, where_clause) = fn_generics.split_for_impl();
quote! {
impl #impl_generics Component for #component_struct_name #ty_generics #where_clause {
fn render(&self, area: Rect, buffer: &mut Buffer) {
let _area_context = reratui::hooks::context::use_context_provider(|| {
reratui::hooks::area::ComponentArea(area)
});
let element = #fn_name(&self.props);
element.render(area, buffer);
}
}
}
}
pub fn generate_type_alias(component_info: &ComponentInfo) -> TokenStream {
let fn_name = &component_info.name;
let component_struct_name = component_info.component_struct_name();
let fn_vis = &component_info.visibility;
quote! {
#fn_vis type #fn_name = #component_struct_name;
}
}
pub fn generate_component_props_impl(
props_struct_name: &syn::Ident,
generics: &Generics,
) -> TokenStream {
let (impl_generics, ty_generics, where_clause) = generics.split_for_impl();
quote! {
impl #impl_generics ComponentProps for #props_struct_name #ty_generics #where_clause {
fn get_children(&self) -> Vec<Element> {
self.children.clone()
}
fn set_children(&mut self, children: Vec<Element>) {
self.children = children;
}
}
}
}
pub fn generate_docs(
component_info: &ComponentInfo,
metadata: &GeneratedComponentMetadata,
) -> TokenStream {
let fn_name = &metadata.function_name;
let component_type_desc = match &component_info.component_type {
ComponentType::PropsBased { .. } => "props-based",
ComponentType::DirectParams { .. } => "direct parameters",
ComponentType::NoParams => "no parameters",
};
let doc_comment = format!(
"Generated component struct for `{}` ({} component)\n\nGenerated methods: {}",
fn_name,
component_type_desc,
metadata.generated_methods.join(", ")
);
let children_support_doc = if metadata.supports_children {
"This component supports children elements."
} else {
"This component does not support children elements."
};
let struct_info = if let Some(props_name) = &metadata.props_struct_name {
format!(
"Props struct: {}, Component struct: {}",
props_name, metadata.component_struct_name
)
} else {
format!("Component struct: {}", metadata.component_struct_name)
};
quote! {
#[doc = #doc_comment]
#[doc = #children_support_doc]
#[doc = #struct_info]
}
}
}
struct PropBasedGenerator<'a> {
config: &'a CodeGenConfig,
}
impl<'a> PropBasedGenerator<'a> {
fn new(config: &'a CodeGenConfig) -> Self {
Self { config }
}
}
impl<'a> ComponentGenerator for PropBasedGenerator<'a> {
fn generate(
&self,
_input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
let (props_type, props_param_name) = component_info.props_info().ok_or_else(|| {
ComponentError::internal_error(
"PropBasedGenerator called with non-props-based component",
"generate",
)
})?;
let fn_name = &component_info.name;
let fn_vis = &component_info.visibility;
let fn_block = &component_info.block;
let fn_generics = &component_info.generics;
let return_type = &component_info.return_type;
let component_struct_name = component_info.component_struct_name();
let (_impl_generics, _ty_generics, where_clause) = fn_generics.split_for_impl();
let derives = if self.config.custom_derives.is_empty() {
quote! { #[derive(Clone)] }
} else {
let derive_list = self
.config
.custom_derives
.iter()
.map(|d| syn::Ident::new(d, proc_macro2::Span::call_site()));
quote! { #[derive(#(#derive_list),*)] }
};
let props_struct_name = &component_info.props_struct_name();
let component_struct_impl =
common::generate_component_struct_impl(component_info, props_struct_name);
let component_trait_impl = common::generate_component_trait_impl(component_info);
let type_alias = common::generate_type_alias(component_info);
let debug_info = if self.config.debug_info {
let debug_msg = format!("Props-based component: {}", fn_name);
quote! {
#[doc = #debug_msg]
#[doc = "Generated with debug information enabled"]
}
} else {
quote! {}
};
let expanded = quote! {
#debug_info
#[allow(non_snake_case)]
#fn_vis fn #fn_name #fn_generics(#props_param_name: &#props_type) -> #return_type #where_clause #fn_block
#derives
#fn_vis struct #component_struct_name #fn_generics #where_clause {
props: #props_type,
}
#component_struct_impl
#component_trait_impl
#type_alias
};
Ok(expanded)
}
}
struct DirectParamsGenerator<'a> {
config: &'a CodeGenConfig,
}
impl<'a> DirectParamsGenerator<'a> {
fn new(config: &'a CodeGenConfig) -> Self {
Self { config }
}
}
impl<'a> ComponentGenerator for DirectParamsGenerator<'a> {
fn generate(
&self,
_input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
let parameters = match &component_info.component_type {
ComponentType::DirectParams { parameters } => parameters,
_ => {
return Err(ComponentError::internal_error(
"DirectParamsGenerator called with non-direct-params component",
"generate",
));
}
};
let fn_name = &component_info.name;
let fn_vis = &component_info.visibility;
let fn_block = &component_info.block;
let fn_generics = &component_info.generics;
let return_type = &component_info.return_type;
let props_struct_name = component_info.props_struct_name();
let component_struct_name = component_info.component_struct_name();
let original_fn_name = component_info.original_function_name();
let (impl_generics, ty_generics, where_clause) = fn_generics.split_for_impl();
let derives = if self.config.custom_derives.is_empty() {
quote! { #[derive(Clone)] }
} else {
let derive_list = self
.config
.custom_derives
.iter()
.map(|d| syn::Ident::new(d, proc_macro2::Span::call_site()));
quote! { #[derive(#(#derive_list),*)] }
};
let prop_fields = parameters.iter().map(|param| {
let name = ¶m.name;
let param_type = ¶m.param_type;
quote! { pub #name: #param_type }
});
let default_fields = parameters.iter().map(|param| {
let name = ¶m.name;
quote! { #name: Default::default() }
});
let builder_methods = parameters.iter().map(|param| {
let name = ¶m.name;
let param_type = ¶m.param_type;
quote! {
pub fn #name(mut self, #name: #param_type) -> Self {
self.#name = #name;
self
}
}
});
let param_names = parameters.iter().map(|param| ¶m.name);
let param_list = quote! { #(props.#param_names.clone()),* };
let original_params = parameters.iter().map(|param| {
let name = ¶m.name;
let param_type = ¶m.param_type;
quote! { #name: #param_type }
});
let component_props_impl =
common::generate_component_props_impl(&props_struct_name, fn_generics);
let component_struct_impl =
common::generate_component_struct_impl(component_info, &props_struct_name);
let component_trait_impl = common::generate_component_trait_impl(component_info);
let type_alias = common::generate_type_alias(component_info);
let expanded = quote! {
#derives
#fn_vis struct #props_struct_name #fn_generics #where_clause {
#(#prop_fields,)*
pub children: Vec<Element>,
}
impl #impl_generics Default for #props_struct_name #ty_generics #where_clause {
fn default() -> Self {
Self {
#(#default_fields,)*
children: Vec::new(),
}
}
}
impl #impl_generics #props_struct_name #ty_generics #where_clause {
#(#builder_methods)*
pub fn with_children(mut self, children: Vec<Element>) -> Self {
self.children = children;
self
}
}
#component_props_impl
#[allow(non_snake_case)]
fn #original_fn_name #fn_generics(#(#original_params),*) -> #return_type #where_clause #fn_block
#[allow(non_snake_case)]
#fn_vis fn #fn_name #fn_generics(props: &#props_struct_name) -> #return_type #where_clause {
#original_fn_name(#param_list)
}
#[derive(Clone)]
#fn_vis struct #component_struct_name #fn_generics #where_clause {
props: #props_struct_name,
}
#component_struct_impl
#component_trait_impl
#type_alias
};
Ok(expanded)
}
}
struct NoParamsGenerator<'a> {
config: &'a CodeGenConfig,
}
impl<'a> NoParamsGenerator<'a> {
fn new(config: &'a CodeGenConfig) -> Self {
Self { config }
}
}
impl<'a> ComponentGenerator for NoParamsGenerator<'a> {
fn generate(
&self,
_input: &ItemFn,
component_info: &ComponentInfo,
) -> ComponentResult<TokenStream> {
match &component_info.component_type {
ComponentType::NoParams => {}
_ => {
return Err(ComponentError::internal_error(
"NoParamsGenerator called with non-no-params component",
"generate",
));
}
};
let fn_name = &component_info.name;
let fn_vis = &component_info.visibility;
let fn_block = &component_info.block;
let fn_generics = &component_info.generics;
let return_type = &component_info.return_type;
let props_struct_name = component_info.props_struct_name();
let component_struct_name = component_info.component_struct_name();
let original_fn_name = component_info.original_function_name();
let (impl_generics, ty_generics, where_clause) = fn_generics.split_for_impl();
let derives = if self.config.custom_derives.is_empty() {
quote! { #[derive(Clone, Default)] }
} else {
let mut derive_list: Vec<_> = self
.config
.custom_derives
.iter()
.map(|d| syn::Ident::new(d, proc_macro2::Span::call_site()))
.collect();
if !self.config.custom_derives.contains(&"Default".to_string()) {
derive_list.push(syn::Ident::new("Default", proc_macro2::Span::call_site()));
}
quote! { #[derive(#(#derive_list),*)] }
};
let component_props_impl =
common::generate_component_props_impl(&props_struct_name, fn_generics);
let component_struct_impl =
common::generate_component_struct_impl(component_info, &props_struct_name);
let component_trait_impl = common::generate_component_trait_impl(component_info);
let type_alias = common::generate_type_alias(component_info);
let expanded = quote! {
#derives
#fn_vis struct #props_struct_name #fn_generics #where_clause {
pub children: Vec<Element>,
}
impl #impl_generics #props_struct_name #ty_generics #where_clause {
pub fn with_children(mut self, children: Vec<Element>) -> Self {
self.children = children;
self
}
}
#component_props_impl
#[allow(non_snake_case)]
fn #original_fn_name #fn_generics() -> #return_type #where_clause #fn_block
#[allow(non_snake_case)]
#fn_vis fn #fn_name #fn_generics(_props: &#props_struct_name) -> #return_type #where_clause {
#original_fn_name()
}
#[derive(Clone)]
#fn_vis struct #component_struct_name #fn_generics #where_clause {
props: #props_struct_name,
}
#component_struct_impl
#component_trait_impl
#type_alias
};
Ok(expanded)
}
}