Pārlūkot izejas kodu

Parse raw elements, attributes, and web components in rsx (#2655)

* parse raw elements and attributes in rsx

---------

Co-authored-by: Jonathan Kelley <jkelleyrtp@gmail.com>
Evan Almloff 11 mēneši atpakaļ
vecāks
revīzija
3bb9a535d8

+ 4 - 0
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!" })}

+ 3 - 3
packages/rsx/src/attribute.rs

@@ -63,8 +63,8 @@ pub struct Attribute {
 impl Parse for Attribute {
     fn parse(content: ParseStream) -> syn::Result<Self> {
         // 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::<LitStr>()?),
-            false => AttributeName::BuiltIn(Ident::parse_any(content)?),
+            false => AttributeName::BuiltIn(parse_raw_ident(content)?),
         };
 
         // Ensure there's a colon

+ 13 - 1
packages/rsx/src/element.rs

@@ -358,7 +358,8 @@ impl ToTokens for ElementName {
 
 impl Parse for ElementName {
     fn parse(stream: ParseStream) -> Result<Self> {
-        let raw = Punctuated::<Ident, Token![-]>::parse_separated_nonempty(stream)?;
+        let raw =
+            Punctuated::<Ident, Token![-]>::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();
+}

+ 4 - 3
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::<Element>()?));
         }
 
@@ -86,8 +87,8 @@ impl Parse for BodyNode {
         //
         // example:
         // div {}
-        if stream.peek(Ident) && !stream.peek2(Token![::]) {
-            let ident = stream.fork().parse::<Ident>().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();
 

+ 2 - 2
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::<Ident>() else {
+        let Ok(ident) = stream.fork().call(Ident::parse_any) else {
             return false;
         };
 

+ 16 - 0
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<Ident> {
+    // 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()))
+}