mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-10 06:34:20 +00:00
feat: Support generic components in rsx!()
macro
This commit is contained in:
parent
fe1279a1c5
commit
a55b56b403
3 changed files with 126 additions and 34 deletions
|
@ -18,19 +18,63 @@ use quote::{quote, ToTokens, TokenStreamExt};
|
|||
use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
token, Expr, Ident, LitStr, Result, Token,
|
||||
token, AngleBracketedGenericArguments, Expr, Ident, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
pub struct Component {
|
||||
pub name: syn::Path,
|
||||
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
|
||||
pub body: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
pub fn validate_component_path(path: &syn::Path) -> Result<()> {
|
||||
// ensure path segments doesn't have PathArguments, only the last
|
||||
// segment is allowed to have one.
|
||||
if path
|
||||
.segments
|
||||
.iter()
|
||||
.take(path.segments.len() - 1)
|
||||
.any(|seg| seg.arguments != PathArguments::None)
|
||||
{
|
||||
component_path_cannot_have_arguments!(path);
|
||||
}
|
||||
|
||||
// ensure last segment only have value of None or AngleBracketed
|
||||
if !matches!(
|
||||
path.segments.last().unwrap().arguments,
|
||||
PathArguments::None | PathArguments::AngleBracketed(_)
|
||||
) {
|
||||
invalid_component_path!(path);
|
||||
}
|
||||
|
||||
// if matches!(
|
||||
// path.segments.last().unwrap().arguments,
|
||||
// PathArguments::AngleBracketed(_)
|
||||
// ) {
|
||||
// proc_macro_error::abort!(path, "path: {}", path.to_token_stream().to_string());
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for Component {
|
||||
fn parse(stream: ParseStream) -> Result<Self> {
|
||||
let name = syn::Path::parse_mod_style(stream)?;
|
||||
let mut name = stream.parse::<syn::Path>()?;
|
||||
Component::validate_component_path(&name)?;
|
||||
|
||||
// extract the path arguments from the path into prop_gen_args
|
||||
let prop_gen_args = name.segments.last_mut().and_then(|seg| {
|
||||
if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
|
||||
seg.arguments = PathArguments::None;
|
||||
Some(args)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
});
|
||||
|
||||
let content: ParseBuffer;
|
||||
|
||||
|
@ -64,6 +108,7 @@ impl Parse for Component {
|
|||
|
||||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
body,
|
||||
children,
|
||||
manual_props,
|
||||
|
@ -74,6 +119,7 @@ impl Parse for Component {
|
|||
impl ToTokens for Component {
|
||||
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
||||
let name = &self.name;
|
||||
let prop_gen_args = &self.prop_gen_args;
|
||||
|
||||
let mut has_key = None;
|
||||
|
||||
|
@ -101,7 +147,10 @@ impl ToTokens for Component {
|
|||
}}
|
||||
}
|
||||
None => {
|
||||
let mut toks = quote! { fc_to_builder(#name) };
|
||||
let mut toks = match prop_gen_args {
|
||||
Some(gen_args) => quote! { fc_to_builder::#gen_args(#name) },
|
||||
None => quote! { fc_to_builder(#name) },
|
||||
};
|
||||
for field in &self.body {
|
||||
match field.name.to_string().as_str() {
|
||||
"key" => {
|
||||
|
|
|
@ -13,3 +13,29 @@ macro_rules! attr_after_element {
|
|||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! component_path_cannot_have_arguments {
|
||||
($span:expr) => {
|
||||
proc_macro_error::abort!(
|
||||
$span,
|
||||
"expected a path without arguments";
|
||||
help = "try remove the path arguments"
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! component_ident_cannot_use_paren {
|
||||
($span:expr, $com_name:ident) => {
|
||||
proc_macro_error::abort!(
|
||||
$span,
|
||||
"Invalid component syntax";
|
||||
help = "try replace {} (..) to {} {{..}}", $com_name, $com_name;
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! invalid_component_path {
|
||||
($span:expr) => {
|
||||
proc_macro_error::abort!($span, "Invalid component path syntax")
|
||||
};
|
||||
}
|
||||
|
|
|
@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
|
|||
use quote::{quote, ToTokens, TokenStreamExt};
|
||||
use syn::{
|
||||
parse::{Parse, ParseStream},
|
||||
token, Expr, LitStr, Result, Token,
|
||||
token, Expr, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
/*
|
||||
|
@ -28,39 +28,56 @@ impl Parse for BodyNode {
|
|||
return Ok(BodyNode::Text(stream.parse()?));
|
||||
}
|
||||
|
||||
// div {} -> el
|
||||
// Div {} -> comp
|
||||
if stream.peek(syn::Ident) && stream.peek2(token::Brace) {
|
||||
if stream
|
||||
.fork()
|
||||
.parse::<Ident>()?
|
||||
.to_string()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_uppercase()
|
||||
{
|
||||
return Ok(BodyNode::Component(stream.parse()?));
|
||||
} else {
|
||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||
let body_stream = stream.fork();
|
||||
if let Ok(path) = body_stream.parse::<syn::Path>() {
|
||||
// this is an Element if path match of:
|
||||
// - one ident
|
||||
// - followed by `{`
|
||||
// - have no `PathArguments`
|
||||
// - starts with lowercase
|
||||
//
|
||||
// example:
|
||||
// div {}
|
||||
if let Some(ident) = path.get_ident() {
|
||||
if body_stream.peek(token::Brace)
|
||||
&& ident
|
||||
.to_string()
|
||||
.chars()
|
||||
.next()
|
||||
.unwrap()
|
||||
.is_ascii_lowercase()
|
||||
{
|
||||
return Ok(BodyNode::Element(stream.parse::<Element>()?));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// component() -> comp
|
||||
// ::component {} -> comp
|
||||
// ::component () -> comp
|
||||
if (stream.peek(syn::Ident) && stream.peek2(token::Paren))
|
||||
|| (stream.peek(Token![::]))
|
||||
|| (stream.peek(Token![:]) && stream.peek2(Token![:]))
|
||||
{
|
||||
return Ok(BodyNode::Component(stream.parse::<Component>()?));
|
||||
}
|
||||
// Otherwise this should be Component, allowed syntax:
|
||||
// - syn::Path
|
||||
// - PathArguments can only apper in last segment
|
||||
// - followed by `{` or `(`, note `(` cannot be used with one ident
|
||||
//
|
||||
// example
|
||||
// Div {}
|
||||
// ::Div {}
|
||||
// crate::Div {}
|
||||
// component()
|
||||
// ::component {}
|
||||
// ::component ()
|
||||
// crate::component{}
|
||||
// crate::component()
|
||||
// Input<'_, String> {}
|
||||
// crate::Input<'_, i32> {}
|
||||
if body_stream.peek(token::Brace) || body_stream.peek(token::Paren) {
|
||||
// this syntax is not allowd:
|
||||
// Div () -> comp
|
||||
if path.segments.len() == 1 && body_stream.peek(token::Paren) {
|
||||
let com_ident = &path.segments.iter().next().unwrap().ident;
|
||||
component_ident_cannot_use_paren!(path, com_ident);
|
||||
}
|
||||
|
||||
// crate::component{} -> comp
|
||||
// crate::component() -> comp
|
||||
if let Ok(pat) = stream.fork().parse::<syn::Path>() {
|
||||
if pat.segments.len() > 1 {
|
||||
return Ok(BodyNode::Component(stream.parse::<Component>()?));
|
||||
Component::validate_component_path(&path)?;
|
||||
|
||||
return Ok(BodyNode::Component(stream.parse()?));
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue