diff --git a/packages/core-macro/src/rsx/component.rs b/packages/core-macro/src/rsx/component.rs index 5b0a38b87..d0ade9058 100644 --- a/packages/core-macro/src/rsx/component.rs +++ b/packages/core-macro/src/rsx/component.rs @@ -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, pub body: Vec, pub children: Vec, pub manual_props: Option, } +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 { - let name = syn::Path::parse_mod_style(stream)?; + let mut name = stream.parse::()?; + 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" => { diff --git a/packages/core-macro/src/rsx/errors.rs b/packages/core-macro/src/rsx/errors.rs index 27e2c7f98..07134e4f0 100644 --- a/packages/core-macro/src/rsx/errors.rs +++ b/packages/core-macro/src/rsx/errors.rs @@ -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") + }; +} diff --git a/packages/core-macro/src/rsx/node.rs b/packages/core-macro/src/rsx/node.rs index d5d701767..fa651cebf 100644 --- a/packages/core-macro/src/rsx/node.rs +++ b/packages/core-macro/src/rsx/node.rs @@ -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::()? - .to_string() - .chars() - .next() - .unwrap() - .is_ascii_uppercase() - { - return Ok(BodyNode::Component(stream.parse()?)); - } else { - return Ok(BodyNode::Element(stream.parse::()?)); + let body_stream = stream.fork(); + if let Ok(path) = body_stream.parse::() { + // 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::()?)); + } } - } - // 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::()?)); - } + // 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::() { - if pat.segments.len() > 1 { - return Ok(BodyNode::Component(stream.parse::()?)); + Component::validate_component_path(&path)?; + + return Ok(BodyNode::Component(stream.parse()?)); } }