diff --git a/packages/rsx/src/raw_expr.rs b/packages/rsx/src/raw_expr.rs index 408f921c4..ea1808af2 100644 --- a/packages/rsx/src/raw_expr.rs +++ b/packages/rsx/src/raw_expr.rs @@ -1,4 +1,4 @@ -use proc_macro2::TokenStream as TokenStream2; +use proc_macro2::{Delimiter, TokenStream as TokenStream2, TokenTree}; use quote::ToTokens; use std::hash; use syn::{parse::Parse, spanned::Spanned, token::Brace, Expr}; @@ -15,8 +15,33 @@ pub struct PartialExpr { impl Parse for PartialExpr { fn parse(input: syn::parse::ParseStream) -> syn::Result { - // Parse as an expression if there is no brace - if !input.peek(syn::token::Brace) { + // Input is a braced expression if it's a braced group + // followed by either of the following: + // - the end of the stream + // - a comma + // - another braced group + // - an identifier + // - a string literal + let mut is_braced = false; + if let Some((TokenTree::Group(group), next)) = input.fork().cursor().token_tree() { + let next_char_is_a_comma = next.punct().is_some_and(|(tt, _)| tt.as_char() == ','); + let next_is_a_braced_exp = next.group(Delimiter::Brace).is_some(); + let next_is_an_ident = next.ident().is_some(); + let next_is_a_string_literal = next.literal().is_some(); + + if group.delimiter() == Delimiter::Brace + && (next.eof() + || next_char_is_a_comma + || next_is_a_braced_exp + || next_is_an_ident + || next_is_a_string_literal) + { + is_braced = true + } + }; + + // Parse as an expression if it's not braced + if !is_braced { let expr = input.parse::()?; return Ok(Self { brace: None, diff --git a/packages/rsx/tests/parsing.rs b/packages/rsx/tests/parsing.rs index aae64d7a3..6f00e877e 100644 --- a/packages/rsx/tests/parsing.rs +++ b/packages/rsx/tests/parsing.rs @@ -71,6 +71,7 @@ fn complex_kitchen_sink() { // complex_carry button { class: "flex items-center pl-3 py-3 pr-2 text-gray-500 hover:bg-indigo-50 rounded", + width: {"100%"}.to_string(), onclick: move |evt| { show_user_menu.set(!show_user_menu.get()); evt.cancel_bubble(); @@ -167,3 +168,27 @@ fn key_cannot_be_static() { println!("{:?}", parsed.body.diagnostics); assert!(!parsed.body.diagnostics.is_empty()); } + +#[test] +fn braced_expressions() { + let item = quote::quote! { + div { + width: {100} - 50, + width: {"100%"}.to_string(), + width: {|| "100%"}(), + } + // Partial expressions in braces rsx should be allowed and output as-is + // for autocomplete + {partial.} + div {} + {partial.} + // Comments should be ignored + div {} + {partial.} + "hello world" + {partial.} + if true {} + }; + + let _cb: CallBody = syn::parse2(item).unwrap(); +}