|
@@ -19,18 +19,18 @@ use quote::{quote, ToTokens, TokenStreamExt};
|
|
|
use syn::{
|
|
|
ext::IdentExt,
|
|
|
parse::{Parse, ParseBuffer, ParseStream},
|
|
|
- token, Expr, ExprClosure, Ident, Result, Token,
|
|
|
+ token, Error, Expr, ExprClosure, Ident, Result, Token,
|
|
|
};
|
|
|
|
|
|
-pub struct Component {
|
|
|
+pub struct Component<const AS: HTML_OR_RSX> {
|
|
|
// accept any path-like argument
|
|
|
name: syn::Path,
|
|
|
- body: Vec<ComponentField>,
|
|
|
- children: Vec<BodyNode>,
|
|
|
+ body: Vec<ComponentField<AS>>,
|
|
|
+ children: Vec<BodyNode<AS>>,
|
|
|
manual_props: Option<Expr>,
|
|
|
}
|
|
|
|
|
|
-impl Parse for Component {
|
|
|
+impl Parse for Component<AS_RSX> {
|
|
|
fn parse(stream: ParseStream) -> Result<Self> {
|
|
|
// let name = s.parse::<syn::ExprPath>()?;
|
|
|
// todo: look into somehow getting the crate/super/etc
|
|
@@ -41,21 +41,45 @@ impl Parse for Component {
|
|
|
let content: ParseBuffer;
|
|
|
syn::braced!(content in stream);
|
|
|
|
|
|
- let mut body: Vec<ComponentField> = Vec::new();
|
|
|
- let mut children: Vec<BodyNode> = Vec::new();
|
|
|
+ let mut body: Vec<ComponentField<AS_RSX>> = Vec::new();
|
|
|
+ let mut children: Vec<BodyNode<AS_RSX>> = Vec::new();
|
|
|
let mut manual_props = None;
|
|
|
|
|
|
- parse_component_body(
|
|
|
- &content,
|
|
|
- &BodyParseConfig {
|
|
|
- allow_children: true,
|
|
|
- allow_fields: true,
|
|
|
- allow_manual_props: true,
|
|
|
- },
|
|
|
- &mut body,
|
|
|
- &mut children,
|
|
|
- &mut manual_props,
|
|
|
- )?;
|
|
|
+ let cfg: BodyParseConfig<AS_RSX> = BodyParseConfig {
|
|
|
+ allow_children: true,
|
|
|
+ allow_fields: true,
|
|
|
+ allow_manual_props: true,
|
|
|
+ };
|
|
|
+
|
|
|
+ cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
|
|
|
+
|
|
|
+ Ok(Self {
|
|
|
+ name,
|
|
|
+ body,
|
|
|
+ children,
|
|
|
+ manual_props,
|
|
|
+ })
|
|
|
+ }
|
|
|
+}
|
|
|
+impl Parse for Component<AS_HTML> {
|
|
|
+ fn parse(stream: ParseStream) -> Result<Self> {
|
|
|
+ let name = syn::Path::parse_mod_style(stream)?;
|
|
|
+
|
|
|
+ // parse the guts
|
|
|
+ let content: ParseBuffer;
|
|
|
+ syn::braced!(content in stream);
|
|
|
+
|
|
|
+ let mut body: Vec<ComponentField<AS_HTML>> = Vec::new();
|
|
|
+ let mut children: Vec<BodyNode<AS_HTML>> = Vec::new();
|
|
|
+ let mut manual_props = None;
|
|
|
+
|
|
|
+ let cfg: BodyParseConfig<AS_HTML> = BodyParseConfig {
|
|
|
+ allow_children: true,
|
|
|
+ allow_fields: true,
|
|
|
+ allow_manual_props: true,
|
|
|
+ };
|
|
|
+
|
|
|
+ cfg.parse_component_body(&content, &mut body, &mut children, &mut manual_props)?;
|
|
|
|
|
|
Ok(Self {
|
|
|
name,
|
|
@@ -66,64 +90,117 @@ impl Parse for Component {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-pub struct BodyParseConfig {
|
|
|
+pub struct BodyParseConfig<const AS: HTML_OR_RSX> {
|
|
|
pub allow_fields: bool,
|
|
|
pub allow_children: bool,
|
|
|
pub allow_manual_props: bool,
|
|
|
}
|
|
|
+impl BodyParseConfig<AS_RSX> {
|
|
|
+ // todo: unify this body parsing for both elements and components
|
|
|
+ // both are style rather ad-hoc, though components are currently more configured
|
|
|
+ pub fn parse_component_body(
|
|
|
+ &self,
|
|
|
+ content: &ParseBuffer,
|
|
|
+ body: &mut Vec<ComponentField<AS_RSX>>,
|
|
|
+ children: &mut Vec<BodyNode<AS_RSX>>,
|
|
|
+ manual_props: &mut Option<Expr>,
|
|
|
+ ) -> Result<()> {
|
|
|
+ 'parsing: loop {
|
|
|
+ // [1] Break if empty
|
|
|
+ if content.is_empty() {
|
|
|
+ break 'parsing;
|
|
|
+ }
|
|
|
|
|
|
-// todo: unify this body parsing for both elements and components
|
|
|
-// both are style rather ad-hoc, though components are currently more configured
|
|
|
-pub fn parse_component_body(
|
|
|
- content: &ParseBuffer,
|
|
|
- cfg: &BodyParseConfig,
|
|
|
- body: &mut Vec<ComponentField>,
|
|
|
- children: &mut Vec<BodyNode>,
|
|
|
- manual_props: &mut Option<Expr>,
|
|
|
-) -> Result<()> {
|
|
|
- 'parsing: loop {
|
|
|
- // [1] Break if empty
|
|
|
- if content.is_empty() {
|
|
|
- break 'parsing;
|
|
|
- }
|
|
|
+ if content.peek(Token![..]) {
|
|
|
+ if !self.allow_manual_props {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ content.parse::<Token![..]>()?;
|
|
|
+ *manual_props = Some(content.parse::<Expr>()?);
|
|
|
+ } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
|
|
+ if !self.allow_fields {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ body.push(content.parse::<ComponentField<AS_RSX>>()?);
|
|
|
+ } else {
|
|
|
+ if !self.allow_children {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "This item is not allowed to accept children.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ children.push(content.parse::<BodyNode<AS_RSX>>()?);
|
|
|
+ }
|
|
|
|
|
|
- if content.peek(Token![..]) {
|
|
|
- if !cfg.allow_manual_props {
|
|
|
- return Err(Error::new(
|
|
|
- content.span(),
|
|
|
- "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
|
|
|
- ));
|
|
|
+ // consume comma if it exists
|
|
|
+ // we don't actually care if there *are* commas between attrs
|
|
|
+ if content.peek(Token![,]) {
|
|
|
+ let _ = content.parse::<Token![,]>();
|
|
|
}
|
|
|
- content.parse::<Token![..]>()?;
|
|
|
- *manual_props = Some(content.parse::<Expr>()?);
|
|
|
- } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
|
|
- if !cfg.allow_fields {
|
|
|
- return Err(Error::new(
|
|
|
- content.span(),
|
|
|
- "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
|
|
|
- ));
|
|
|
+ }
|
|
|
+ Ok(())
|
|
|
+ }
|
|
|
+}
|
|
|
+impl BodyParseConfig<AS_HTML> {
|
|
|
+ // todo: unify this body parsing for both elements and components
|
|
|
+ // both are style rather ad-hoc, though components are currently more configured
|
|
|
+ pub fn parse_component_body(
|
|
|
+ &self,
|
|
|
+ content: &ParseBuffer,
|
|
|
+ body: &mut Vec<ComponentField<AS_HTML>>,
|
|
|
+ children: &mut Vec<BodyNode<AS_HTML>>,
|
|
|
+ manual_props: &mut Option<Expr>,
|
|
|
+ ) -> Result<()> {
|
|
|
+ 'parsing: loop {
|
|
|
+ // [1] Break if empty
|
|
|
+ if content.is_empty() {
|
|
|
+ break 'parsing;
|
|
|
}
|
|
|
- body.push(content.parse::<ComponentField>()?);
|
|
|
- } else {
|
|
|
- if !cfg.allow_children {
|
|
|
- return Err(Error::new(
|
|
|
- content.span(),
|
|
|
- "This item is not allowed to accept children.",
|
|
|
- ));
|
|
|
+
|
|
|
+ if content.peek(Token![..]) {
|
|
|
+ if !self.allow_manual_props {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "Props spread syntax is not allowed in this context. \nMake to only use the elipsis `..` in Components.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ content.parse::<Token![..]>()?;
|
|
|
+ *manual_props = Some(content.parse::<Expr>()?);
|
|
|
+ } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
|
|
+ if !self.allow_fields {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "Property fields is not allowed in this context. \nMake to only use fields in Components or Elements.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ body.push(content.parse::<ComponentField<AS_HTML>>()?);
|
|
|
+ } else {
|
|
|
+ if !self.allow_children {
|
|
|
+ return Err(Error::new(
|
|
|
+ content.span(),
|
|
|
+ "This item is not allowed to accept children.",
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ children.push(content.parse::<BodyNode<AS_HTML>>()?);
|
|
|
}
|
|
|
- children.push(content.parse::<BodyNode>()?);
|
|
|
- }
|
|
|
|
|
|
- // consume comma if it exists
|
|
|
- // we don't actually care if there *are* commas between attrs
|
|
|
- if content.peek(Token![,]) {
|
|
|
- let _ = content.parse::<Token![,]>();
|
|
|
+ // consume comma if it exists
|
|
|
+ // we don't actually care if there *are* commas between attrs
|
|
|
+ if content.peek(Token![,]) {
|
|
|
+ let _ = content.parse::<Token![,]>();
|
|
|
+ }
|
|
|
}
|
|
|
+ Ok(())
|
|
|
}
|
|
|
- Ok(())
|
|
|
}
|
|
|
|
|
|
-impl ToTokens for Component {
|
|
|
+impl<const AS: HTML_OR_RSX> ToTokens for Component<AS> {
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
|
let name = &self.name;
|
|
|
|
|
@@ -195,7 +272,7 @@ impl ToTokens for Component {
|
|
|
}
|
|
|
|
|
|
// the struct's fields info
|
|
|
-pub struct ComponentField {
|
|
|
+pub struct ComponentField<const AS: HTML_OR_RSX> {
|
|
|
name: Ident,
|
|
|
content: ContentField,
|
|
|
}
|
|
@@ -222,7 +299,28 @@ impl ToTokens for ContentField {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl Parse for ComponentField {
|
|
|
+impl Parse for ComponentField<AS_RSX> {
|
|
|
+ fn parse(input: ParseStream) -> Result<Self> {
|
|
|
+ let name = Ident::parse_any(input)?;
|
|
|
+ input.parse::<Token![:]>()?;
|
|
|
+
|
|
|
+ let name_str = name.to_string();
|
|
|
+ let content = if name_str.starts_with("on") {
|
|
|
+ if input.peek(token::Brace) {
|
|
|
+ let content;
|
|
|
+ syn::braced!(content in input);
|
|
|
+ ContentField::OnHandlerRaw(content.parse()?)
|
|
|
+ } else {
|
|
|
+ ContentField::OnHandler(input.parse()?)
|
|
|
+ }
|
|
|
+ } else {
|
|
|
+ ContentField::ManExpr(input.parse::<Expr>()?)
|
|
|
+ };
|
|
|
+
|
|
|
+ Ok(Self { name, content })
|
|
|
+ }
|
|
|
+}
|
|
|
+impl Parse for ComponentField<AS_HTML> {
|
|
|
fn parse(input: ParseStream) -> Result<Self> {
|
|
|
let name = Ident::parse_any(input)?;
|
|
|
input.parse::<Token![:]>()?;
|
|
@@ -244,7 +342,7 @@ impl Parse for ComponentField {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
-impl ToTokens for ComponentField {
|
|
|
+impl<const AS: HTML_OR_RSX> ToTokens for ComponentField<AS> {
|
|
|
fn to_tokens(&self, tokens: &mut TokenStream2) {
|
|
|
let ComponentField { name, content, .. } = self;
|
|
|
tokens.append_all(quote! {
|