feat: Support generic components in rsx!() macro

This commit is contained in:
Muhannad Alrusayni committed 2022-06-04 13:40:50 +03:00
1 parent fe1279a1c5
commit a55b56b403
3 files changed
+126 -34

No files matched your search

+52 -3
View File
@@ -18,19 +18,63 @@ use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
ext::IdentExt, ext::IdentExt,
parse::{Parse, ParseBuffer, ParseStream}, parse::{Parse, ParseBuffer, ParseStream},
token, Expr, Ident, LitStr, Result, Token, token, AngleBracketedGenericArguments, Expr, Ident, LitStr, PathArguments, Result, Token,
}; };
pub struct Component { pub struct Component {
pub name: syn::Path, pub name: syn::Path,
pub prop_gen_args: Option<AngleBracketedGenericArguments>,
pub body: Vec<ComponentField>, pub body: Vec<ComponentField>,
pub children: Vec<BodyNode>, pub children: Vec<BodyNode>,
pub manual_props: Option<Expr>, 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 { impl Parse for Component {
fn parse(stream: ParseStream) -> Result<Self> { 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; let content: ParseBuffer;
@@ -64,6 +108,7 @@ impl Parse for Component {
Ok(Self { Ok(Self {
name, name,
prop_gen_args,
body, body,
children, children,
manual_props, manual_props,
@@ -74,6 +119,7 @@ impl Parse for Component {
impl ToTokens for Component { impl ToTokens for Component {
fn to_tokens(&self, tokens: &mut TokenStream2) { fn to_tokens(&self, tokens: &mut TokenStream2) {
let name = &self.name; let name = &self.name;
let prop_gen_args = &self.prop_gen_args;
let mut has_key = None; let mut has_key = None;
@@ -101,7 +147,10 @@ impl ToTokens for Component {
}} }}
} }
None => { 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 { for field in &self.body {
match field.name.to_string().as_str() { match field.name.to_string().as_str() {
"key" => { "key" => {
+26
View File
@@ -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")
};
}
+48 -31
View File
@@ -4,7 +4,7 @@ use proc_macro2::TokenStream as TokenStream2;
use quote::{quote, ToTokens, TokenStreamExt}; use quote::{quote, ToTokens, TokenStreamExt};
use syn::{ use syn::{
parse::{Parse, ParseStream}, 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()?)); return Ok(BodyNode::Text(stream.parse()?));
} }
// div {} -> el let body_stream = stream.fork();
// Div {} -> comp if let Ok(path) = body_stream.parse::<syn::Path>() {
if stream.peek(syn::Ident) && stream.peek2(token::Brace) { // this is an Element if path match of:
if stream // - one ident
.fork() // - followed by `{`
.parse::<Ident>()? // - have no `PathArguments`
.to_string() // - starts with lowercase
.chars() //
.next() // example:
.unwrap() // div {}
.is_ascii_uppercase() if let Some(ident) = path.get_ident() {
{ if body_stream.peek(token::Brace)
return Ok(BodyNode::Component(stream.parse()?)); && ident
} else { .to_string()
return Ok(BodyNode::Element(stream.parse::<Element>()?)); .chars()
.next()
.unwrap()
.is_ascii_lowercase()
{
return Ok(BodyNode::Element(stream.parse::<Element>()?));
}
} }
}
// component() -> comp // Otherwise this should be Component, allowed syntax:
// ::component {} -> comp // - syn::Path
// ::component () -> comp // - PathArguments can only apper in last segment
if (stream.peek(syn::Ident) && stream.peek2(token::Paren)) // - followed by `{` or `(`, note `(` cannot be used with one ident
|| (stream.peek(Token![::])) //
|| (stream.peek(Token![:]) && stream.peek2(Token![:])) // example
{ // Div {}
return Ok(BodyNode::Component(stream.parse::<Component>()?)); // ::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 Component::validate_component_path(&path)?;
// crate::component() -> comp
if let Ok(pat) = stream.fork().parse::<syn::Path>() { return Ok(BodyNode::Component(stream.parse()?));
if pat.segments.len() > 1 {
return Ok(BodyNode::Component(stream.parse::<Component>()?));
} }
} }