Merge branch 'master' into fix-event-bubbling
This commit is contained in:
1 parent
c8fca7b321
commit
98335bd9a6
368 files changed
+1877
-13498
No files matched your search
@@ -0,0 +1,152 @@
|
||||
use crate::component_body::{ComponentBody, DeserializerArgs};
|
||||
use crate::component_body_deserializers::inline_props::InlinePropsDeserializerArgs;
|
||||
use constcat::concat;
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::*;
|
||||
|
||||
pub(crate) const COMPONENT_ARG_CASE_CHECK_ERROR: &str = concat!(
|
||||
"This component does not use PascalCase. \
|
||||
To ignore this check, pass the \"",
|
||||
crate::COMPONENT_ARG_CASE_CHECK_OFF,
|
||||
"\" argument, like so: #[component(",
|
||||
crate::COMPONENT_ARG_CASE_CHECK_OFF,
|
||||
")]"
|
||||
);
|
||||
|
||||
const INNER_FN_NAME: &str = "__dx_inner_comp";
|
||||
|
||||
fn get_out_comp_fn(orig_comp_fn: &ItemFn, cx_pat: &Pat) -> ItemFn {
|
||||
let inner_comp_ident = Ident::new(INNER_FN_NAME, orig_comp_fn.sig.ident.span());
|
||||
|
||||
let inner_comp_fn = ItemFn {
|
||||
sig: Signature {
|
||||
ident: inner_comp_ident.clone(),
|
||||
..orig_comp_fn.sig.clone()
|
||||
},
|
||||
..orig_comp_fn.clone()
|
||||
};
|
||||
|
||||
ItemFn {
|
||||
block: parse_quote! {
|
||||
{
|
||||
#[warn(non_snake_case)]
|
||||
#[inline(always)]
|
||||
#inner_comp_fn
|
||||
#inner_comp_ident (#cx_pat)
|
||||
}
|
||||
},
|
||||
..orig_comp_fn.clone()
|
||||
}
|
||||
}
|
||||
|
||||
/// The args and deserializing implementation for the [`crate::component`] macro.
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentDeserializerArgs {
|
||||
pub case_check: bool,
|
||||
}
|
||||
|
||||
/// The output fields and [`ToTokens`] implementation for the [`crate::component`] macro.
|
||||
#[derive(Clone)]
|
||||
pub struct ComponentDeserializerOutput {
|
||||
pub comp_fn: ItemFn,
|
||||
pub props_struct: Option<ItemStruct>,
|
||||
}
|
||||
|
||||
impl ToTokens for ComponentDeserializerOutput {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let comp_fn = &self.comp_fn;
|
||||
let props_struct = &self.props_struct;
|
||||
|
||||
tokens.append_all(quote! {
|
||||
#props_struct
|
||||
#[allow(non_snake_case)]
|
||||
#comp_fn
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializerArgs<ComponentDeserializerOutput> for ComponentDeserializerArgs {
|
||||
fn to_output(&self, component_body: &ComponentBody) -> Result<ComponentDeserializerOutput> {
|
||||
let Signature { ident, .. } = &component_body.item_fn.sig;
|
||||
|
||||
if self.case_check && !is_pascal_case(&ident.to_string()) {
|
||||
return Err(Error::new(ident.span(), COMPONENT_ARG_CASE_CHECK_ERROR));
|
||||
}
|
||||
|
||||
if component_body.has_extra_args {
|
||||
Self::deserialize_with_props(component_body)
|
||||
} else {
|
||||
Ok(Self::deserialize_no_props(component_body))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ComponentDeserializerArgs {
|
||||
fn deserialize_no_props(component_body: &ComponentBody) -> ComponentDeserializerOutput {
|
||||
let ComponentBody {
|
||||
item_fn,
|
||||
cx_pat_type,
|
||||
..
|
||||
} = component_body;
|
||||
let cx_pat = &cx_pat_type.pat;
|
||||
|
||||
let comp_fn = get_out_comp_fn(item_fn, cx_pat);
|
||||
|
||||
ComponentDeserializerOutput {
|
||||
comp_fn,
|
||||
props_struct: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn deserialize_with_props(
|
||||
component_body: &ComponentBody,
|
||||
) -> Result<ComponentDeserializerOutput> {
|
||||
let ComponentBody {
|
||||
item_fn,
|
||||
cx_pat_type,
|
||||
..
|
||||
} = component_body;
|
||||
let cx_pat = &cx_pat_type.pat;
|
||||
|
||||
let comp_parsed = match parse2::<ComponentBody>(quote!(#item_fn)) {
|
||||
Ok(comp_body) => comp_body,
|
||||
Err(e) => {
|
||||
return Err(Error::new(
|
||||
e.span(),
|
||||
format!(
|
||||
"This is probably a bug in our code, please report it! Error: {}",
|
||||
e
|
||||
),
|
||||
))
|
||||
}
|
||||
};
|
||||
|
||||
let inlined_props_output = comp_parsed.deserialize(InlinePropsDeserializerArgs {})?;
|
||||
let props_struct = inlined_props_output.props_struct;
|
||||
let props_fn = inlined_props_output.comp_fn;
|
||||
|
||||
let comp_fn = get_out_comp_fn(&props_fn, cx_pat);
|
||||
|
||||
Ok(ComponentDeserializerOutput {
|
||||
comp_fn,
|
||||
props_struct: Some(props_struct),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn is_pascal_case(input: &str) -> bool {
|
||||
let mut is_next_lowercase = false;
|
||||
|
||||
for c in input.chars() {
|
||||
let is_upper = c.is_ascii_uppercase();
|
||||
|
||||
if (c == '_') || (is_upper && is_next_lowercase) {
|
||||
return false;
|
||||
}
|
||||
|
||||
is_next_lowercase = is_upper;
|
||||
}
|
||||
|
||||
true
|
||||
}
|
||||
@@ -0,0 +1,195 @@
|
||||
use crate::component_body::{ComponentBody, DeserializerArgs};
|
||||
use proc_macro2::TokenStream as TokenStream2;
|
||||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::token::Comma;
|
||||
use syn::{punctuated::Punctuated, *};
|
||||
|
||||
/// The args and deserializing implementation for the [`crate::inline_props`] macro.
|
||||
#[derive(Clone)]
|
||||
pub struct InlinePropsDeserializerArgs;
|
||||
|
||||
/// The output fields and [`ToTokens`] implementation for the [`crate::inline_props`] macro.
|
||||
#[derive(Clone)]
|
||||
pub struct InlinePropsDeserializerOutput {
|
||||
pub comp_fn: ItemFn,
|
||||
pub props_struct: ItemStruct,
|
||||
}
|
||||
|
||||
impl ToTokens for InlinePropsDeserializerOutput {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let function = &self.comp_fn;
|
||||
let props_struct = &self.props_struct;
|
||||
|
||||
tokens.append_all(quote! {
|
||||
#function
|
||||
#props_struct
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
impl DeserializerArgs<InlinePropsDeserializerOutput> for InlinePropsDeserializerArgs {
|
||||
fn to_output(&self, component_body: &ComponentBody) -> Result<InlinePropsDeserializerOutput> {
|
||||
Ok(InlinePropsDeserializerOutput {
|
||||
comp_fn: Self::get_function(component_body),
|
||||
props_struct: Self::get_props_struct(component_body),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl InlinePropsDeserializerArgs {
|
||||
fn get_props_struct(component_body: &ComponentBody) -> ItemStruct {
|
||||
let ComponentBody { item_fn, .. } = component_body;
|
||||
let ItemFn { vis, sig, .. } = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
..
|
||||
} = sig;
|
||||
|
||||
// Skip first arg since that's the context
|
||||
let struct_fields = inputs.iter().skip(1).map(move |f| {
|
||||
match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // Unreachable because of ComponentBody parsing
|
||||
FnArg::Typed(pt) => {
|
||||
let arg_pat = &pt.pat; // Pattern (identifier)
|
||||
let arg_colon = &pt.colon_token;
|
||||
let arg_ty = &pt.ty; // Type
|
||||
let arg_attrs = &pt.attrs; // Attributes
|
||||
|
||||
quote! {
|
||||
#(#arg_attrs)
|
||||
*
|
||||
#vis #arg_pat #arg_colon #arg_ty
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let struct_attrs = if first_lifetime.is_some() {
|
||||
quote! { #[derive(Props)] }
|
||||
} else {
|
||||
quote! { #[derive(Props, PartialEq)] }
|
||||
};
|
||||
|
||||
let struct_generics = if first_lifetime.is_some() {
|
||||
let struct_generics: Punctuated<GenericParam, Comma> = component_body
|
||||
.item_fn
|
||||
.sig
|
||||
.generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.push(parse_quote!( 'a ));
|
||||
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
quote! { <#struct_generics> }
|
||||
} else {
|
||||
quote! { #generics }
|
||||
};
|
||||
|
||||
parse_quote! {
|
||||
#struct_attrs
|
||||
#[allow(non_camel_case_types)]
|
||||
#vis struct #struct_ident #struct_generics
|
||||
{
|
||||
#(#struct_fields),*
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn get_function(component_body: &ComponentBody) -> ItemFn {
|
||||
let ComponentBody {
|
||||
item_fn,
|
||||
cx_pat_type,
|
||||
..
|
||||
} = component_body;
|
||||
let ItemFn {
|
||||
attrs: fn_attrs,
|
||||
vis,
|
||||
sig,
|
||||
block: fn_block,
|
||||
} = item_fn;
|
||||
let Signature {
|
||||
inputs,
|
||||
ident: fn_ident,
|
||||
generics,
|
||||
output: fn_output,
|
||||
asyncness,
|
||||
..
|
||||
} = sig;
|
||||
let Generics { where_clause, .. } = generics;
|
||||
|
||||
let cx_pat = &cx_pat_type.pat;
|
||||
let struct_ident = Ident::new(&format!("{fn_ident}Props"), fn_ident.span());
|
||||
|
||||
// Skip first arg since that's the context
|
||||
let struct_field_names = inputs.iter().skip(1).filter_map(|f| match f {
|
||||
FnArg::Receiver(_) => unreachable!(), // ComponentBody prohibits receiver parameters.
|
||||
FnArg::Typed(t) => Some(&t.pat),
|
||||
});
|
||||
|
||||
let first_lifetime = if let Some(GenericParam::Lifetime(lt)) = generics.params.first() {
|
||||
Some(lt)
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
let (scope_lifetime, fn_generics) = if let Some(lt) = first_lifetime {
|
||||
(quote! { #lt, }, generics.clone())
|
||||
} else {
|
||||
let lifetime: LifetimeParam = parse_quote! { 'a };
|
||||
|
||||
let mut fn_generics = generics.clone();
|
||||
fn_generics
|
||||
.params
|
||||
.insert(0, GenericParam::Lifetime(lifetime.clone()));
|
||||
|
||||
(quote! { #lifetime, }, fn_generics)
|
||||
};
|
||||
|
||||
let generics_no_bounds = {
|
||||
let mut generics = generics.clone();
|
||||
generics.params = generics
|
||||
.params
|
||||
.iter()
|
||||
.map(|it| match it {
|
||||
GenericParam::Type(tp) => {
|
||||
let mut tp = tp.clone();
|
||||
tp.bounds.clear();
|
||||
|
||||
GenericParam::Type(tp)
|
||||
}
|
||||
_ => it.clone(),
|
||||
})
|
||||
.collect();
|
||||
|
||||
generics
|
||||
};
|
||||
|
||||
parse_quote! {
|
||||
#(#fn_attrs)*
|
||||
#asyncness #vis fn #fn_ident #fn_generics (#cx_pat: Scope<#scope_lifetime #struct_ident #generics_no_bounds>) #fn_output
|
||||
#where_clause
|
||||
{
|
||||
let #struct_ident { #(#struct_field_names),* } = &#cx_pat.props;
|
||||
#fn_block
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
//! This module contains all [`ComponentBody`](crate::component_body::ComponentBody) deserializers.
|
||||
|
||||
pub mod component;
|
||||
pub mod inline_props;
|
||||
Reference in new issue
Block a user