123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243 |
- use crate::innerlude::*;
- use proc_macro2::{Span, TokenStream as TokenStream2};
- use quote::ToTokens;
- use syn::{
- ext::IdentExt,
- parse::{Parse, ParseStream},
- spanned::Spanned,
- token::{self},
- Ident, LitStr, Result, Token,
- };
- #[derive(PartialEq, Eq, Clone, Debug)]
- pub enum BodyNode {
- /// div {}
- Element(Element),
- /// Component {}
- Component(Component),
- /// "text {formatted}"
- Text(TextNode),
- /// {expr}
- RawExpr(ExprNode),
- /// for item in items {}
- ForLoop(ForLoop),
- /// if cond {} else if cond {} (else {}?)
- IfChain(IfChain),
- }
- impl Parse for BodyNode {
- fn parse(stream: ParseStream) -> Result<Self> {
- if stream.peek(LitStr) {
- return Ok(BodyNode::Text(stream.parse()?));
- }
- // Transform for loops into into_iter calls
- if stream.peek(Token![for]) {
- return Ok(BodyNode::ForLoop(stream.parse()?));
- }
- // Transform unterminated if statements into terminated optional if statements
- if stream.peek(Token![if]) {
- return Ok(BodyNode::IfChain(stream.parse()?));
- }
- // Match statements are special but have no special arm syntax
- // we could allow arm syntax if we wanted.
- //
- // And it might even backwards compatible? - I think it is with the right fallback
- // -> parse as bodynode (BracedRawExpr will kick in on multiline arms)
- // -> if that fails parse as an expr, since that arm might be a one-liner
- //
- // ```
- // match expr {
- // val => rsx! { div {} },
- // other_val => rsx! { div {} }
- // }
- // ```
- if stream.peek(Token![match]) {
- return Ok(BodyNode::RawExpr(stream.parse()?));
- }
- // Raw expressions need to be wrapped in braces - let RawBracedExpr handle partial expansion
- if stream.peek(token::Brace) {
- return Ok(BodyNode::RawExpr(stream.parse()?));
- }
- // If there's an ident immediately followed by a dash, it's a web component
- // Web components support no namespacing, so just parse it as an element directly
- if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) {
- return Ok(BodyNode::Element(stream.parse::<Element>()?));
- }
- // this is an Element if the path is:
- //
- // - one ident
- // - 1st char is lowercase
- // - no underscores (reserved for components)
- // And it is not:
- // - the start of a path with components
- //
- // example:
- // div {}
- if stream.peek(Ident::peek_any) && !stream.peek2(Token![::]) {
- let ident = parse_raw_ident(&stream.fork()).unwrap();
- let el_name = ident.to_string();
- let first_char = el_name.chars().next().unwrap();
- if first_char.is_ascii_lowercase() && !el_name.contains('_') {
- return Ok(BodyNode::Element(stream.parse::<Element>()?));
- }
- }
- // 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 {} <-- already handled by elements
- // ::component {}
- // crate::component{}
- // Input::<InputProps<'_, i32> {}
- // crate::Input::<InputProps<'_, i32> {}
- Ok(BodyNode::Component(stream.parse()?))
- }
- }
- impl ToTokens for BodyNode {
- fn to_tokens(&self, tokens: &mut TokenStream2) {
- match self {
- BodyNode::Element(ela) => ela.to_tokens(tokens),
- BodyNode::RawExpr(exp) => exp.to_tokens(tokens),
- BodyNode::Text(txt) => txt.to_tokens(tokens),
- BodyNode::ForLoop(floop) => floop.to_tokens(tokens),
- BodyNode::Component(comp) => comp.to_tokens(tokens),
- BodyNode::IfChain(ifchain) => ifchain.to_tokens(tokens),
- }
- }
- }
- impl BodyNode {
- pub fn get_dyn_idx(&self) -> usize {
- match self {
- BodyNode::Text(text) => text.dyn_idx.get(),
- BodyNode::RawExpr(exp) => exp.dyn_idx.get(),
- BodyNode::Component(comp) => comp.dyn_idx.get(),
- BodyNode::ForLoop(floop) => floop.dyn_idx.get(),
- BodyNode::IfChain(chain) => chain.dyn_idx.get(),
- BodyNode::Element(_) => panic!("Cannot get dyn_idx for this node"),
- }
- }
- pub fn set_dyn_idx(&self, idx: usize) {
- match self {
- BodyNode::Text(text) => text.dyn_idx.set(idx),
- BodyNode::RawExpr(exp) => exp.dyn_idx.set(idx),
- BodyNode::Component(comp) => comp.dyn_idx.set(idx),
- BodyNode::ForLoop(floop) => floop.dyn_idx.set(idx),
- BodyNode::IfChain(chain) => chain.dyn_idx.set(idx),
- BodyNode::Element(_) => panic!("Cannot set dyn_idx for this node"),
- }
- }
- pub fn is_litstr(&self) -> bool {
- matches!(self, BodyNode::Text { .. })
- }
- pub fn span(&self) -> Span {
- match self {
- BodyNode::Element(el) => el.name.span(),
- BodyNode::Component(component) => component.name.span(),
- BodyNode::Text(text) => text.input.span(),
- BodyNode::RawExpr(exp) => exp.span(),
- BodyNode::ForLoop(fl) => fl.for_token.span(),
- BodyNode::IfChain(f) => f.if_token.span(),
- }
- }
- pub fn element_children(&self) -> &[BodyNode] {
- match self {
- BodyNode::Element(el) => &el.children,
- _ => panic!("Children not available for this node"),
- }
- }
- pub fn el_name(&self) -> &ElementName {
- match self {
- BodyNode::Element(el) => &el.name,
- _ => panic!("Element name not available for this node"),
- }
- }
- }
- #[cfg(test)]
- mod tests {
- use super::*;
- use quote::quote;
- #[test]
- fn parsing_matches() {
- let element = quote! { div { class: "inline-block mr-4", icons::icon_14 {} } };
- assert!(matches!(
- syn::parse2::<BodyNode>(element).unwrap(),
- BodyNode::Element(_)
- ));
- let text = quote! { "Hello, world!" };
- assert!(matches!(
- syn::parse2::<BodyNode>(text).unwrap(),
- BodyNode::Text(_)
- ));
- let component = quote! { Component {} };
- assert!(matches!(
- syn::parse2::<BodyNode>(component).unwrap(),
- BodyNode::Component(_)
- ));
- let raw_expr = quote! { { 1 + 1 } };
- assert!(matches!(
- syn::parse2::<BodyNode>(raw_expr).unwrap(),
- BodyNode::RawExpr(_)
- ));
- let for_loop = quote! { for item in items {} };
- assert!(matches!(
- syn::parse2::<BodyNode>(for_loop).unwrap(),
- BodyNode::ForLoop(_)
- ));
- let if_chain = quote! { if cond {} else if cond {} };
- assert!(matches!(
- syn::parse2::<BodyNode>(if_chain).unwrap(),
- BodyNode::IfChain(_)
- ));
- let match_expr = quote! {
- match blah {
- val => rsx! { div {} },
- other_val => rsx! { div {} }
- }
- };
- assert!(matches!(
- syn::parse2::<BodyNode>(match_expr).unwrap(),
- BodyNode::RawExpr(_)
- ),);
- let incomplete_component = quote! {
- some::cool::Component
- };
- assert!(matches!(
- syn::parse2::<BodyNode>(incomplete_component).unwrap(),
- BodyNode::Component(_)
- ),);
- }
- }
|