diff --git a/examples/rsx_usage.rs b/examples/rsx_usage.rs index 9085ac2d7..72d5fb3f1 100644 --- a/examples/rsx_usage.rs +++ b/examples/rsx_usage.rs @@ -92,6 +92,10 @@ fn app() -> Element { } } } + use {} + link { + as: "asd" + } // Expressions can be used in element position too: {rsx!(p { "More templating!" })} diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index 1c8f639dd..dd8f2b79e 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -63,8 +63,8 @@ pub struct Attribute { impl Parse for Attribute { fn parse(content: ParseStream) -> syn::Result { // if there's an ident not followed by a colon, it's a shorthand attribute - if content.peek(Ident) && !content.peek2(Token![:]) { - let ident = Ident::parse(content)?; + if content.peek(Ident::peek_any) && !content.peek2(Token![:]) { + let ident = parse_raw_ident(content)?; let comma = content.parse().ok(); return Ok(Attribute { @@ -80,7 +80,7 @@ impl Parse for Attribute { // Parse the name as either a known or custom attribute let name = match content.peek(LitStr) { true => AttributeName::Custom(content.parse::()?), - false => AttributeName::BuiltIn(Ident::parse_any(content)?), + false => AttributeName::BuiltIn(parse_raw_ident(content)?), }; // Ensure there's a colon diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index 2c2dd8df4..c854ea1e4 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -358,7 +358,8 @@ impl ToTokens for ElementName { impl Parse for ElementName { fn parse(stream: ParseStream) -> Result { - let raw = Punctuated::::parse_separated_nonempty(stream)?; + let raw = + Punctuated::::parse_separated_nonempty_with(stream, parse_raw_ident)?; if raw.len() == 1 { Ok(ElementName::Ident(raw.into_iter().next().unwrap())) } else { @@ -607,3 +608,14 @@ fn diagnositcs() { let _parsed: Element = syn::parse2(input).unwrap(); } + +#[test] +fn parses_raw_elements() { + let input = quote::quote! { + use { + "hello" + } + }; + + let _parsed: Element = syn::parse2(input).unwrap(); +} diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 43a2e93d1..7056279e0 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -5,6 +5,7 @@ use crate::innerlude::*; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::ToTokens; use syn::{ + ext::IdentExt, parse::{Parse, ParseStream}, spanned::Spanned, token::{self}, @@ -72,7 +73,7 @@ impl Parse for BodyNode { // 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) && stream.peek2(Token![-]) { + if stream.peek(Ident::peek_any) && stream.peek2(Token![-]) { return Ok(BodyNode::Element(stream.parse::()?)); } @@ -86,8 +87,8 @@ impl Parse for BodyNode { // // example: // div {} - if stream.peek(Ident) && !stream.peek2(Token![::]) { - let ident = stream.fork().parse::().unwrap(); + 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(); diff --git a/packages/rsx/src/rsx_block.rs b/packages/rsx/src/rsx_block.rs index b15c36b4d..93154f2e1 100644 --- a/packages/rsx/src/rsx_block.rs +++ b/packages/rsx/src/rsx_block.rs @@ -116,7 +116,7 @@ impl RsxBlock { } // Parse unambiguous attributes - these can't be confused with anything - if (content.peek(LitStr) || content.peek(Ident) || content.peek(Ident::peek_any)) + if (content.peek(LitStr) || content.peek(Ident::peek_any)) && content.peek2(Token![:]) && !content.peek3(Token![:]) { @@ -239,7 +239,7 @@ impl RsxBlock { } fn peek_lowercase_ident(stream: &ParseStream) -> bool { - let Ok(ident) = stream.fork().parse::() else { + let Ok(ident) = stream.fork().call(Ident::parse_any) else { return false; }; diff --git a/packages/rsx/src/util.rs b/packages/rsx/src/util.rs index ee0adb157..2c945d6f9 100644 --- a/packages/rsx/src/util.rs +++ b/packages/rsx/src/util.rs @@ -5,6 +5,11 @@ use internment::Intern; use proc_macro2::TokenStream as TokenStream2; use std::{fmt::Debug, hash::Hash}; +use syn::{ + ext::IdentExt, + parse::{Parse, ParseBuffer}, + Ident, +}; /// interns a object into a static object, resusing the value if it already exists #[cfg(feature = "hot_reload")] @@ -27,3 +32,14 @@ impl PrettyUnparse for TokenStream2 { prettier_please::unparse_expr(&parsed) } } + +/// Parse a raw ident and return a new ident with the r# prefix added +pub fn parse_raw_ident(parse_buffer: &ParseBuffer) -> syn::Result { + // First try to parse as a normal ident + if let Ok(ident) = Ident::parse(parse_buffer) { + return Ok(ident); + } + // If that fails, try to parse as a raw ident + let ident = Ident::parse_any(parse_buffer)?; + Ok(Ident::new_raw(&ident.to_string(), ident.span())) +}