diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index b64415649..c583dc593 100644 --- a/packages/autofmt/src/element.rs +++ b/packages/autofmt/src/element.rs @@ -224,6 +224,15 @@ impl Writer<'_> { fn write_attribute_value(&mut self, value: &ElementAttrValue) -> Result { match value { + ElementAttrValue::AttrOptionalExpr { condition, value } => { + write!( + self.out, + "if {condition} {{ ", + condition = prettyplease::unparse_expr(condition), + )?; + self.write_attribute_value(value)?; + write!(self.out, " }}")?; + } ElementAttrValue::AttrLiteral(value) => { write!(self.out, "{value}", value = ifmt_to_string(value))?; } diff --git a/packages/autofmt/src/writer.rs b/packages/autofmt/src/writer.rs index c96e96c93..788007319 100644 --- a/packages/autofmt/src/writer.rs +++ b/packages/autofmt/src/writer.rs @@ -132,6 +132,39 @@ impl<'a> Writer<'a> { Ok(()) } + pub(crate) fn attr_value_len(&mut self, value: &ElementAttrValue) -> usize { + match value { + ElementAttrValue::AttrOptionalExpr { condition, value } => { + let condition_len = self.retrieve_formatted_expr(condition).len(); + let value_len = self.attr_value_len(value); + + condition_len + value_len + 6 + } + ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(), + ElementAttrValue::AttrExpr(expr) => expr.span().line_length(), + ElementAttrValue::EventTokens(tokens) => { + let location = Location::new(tokens.span().start()); + + let len = if let std::collections::hash_map::Entry::Vacant(e) = + self.cached_formats.entry(location) + { + let formatted = prettyplease::unparse_expr(tokens); + let len = if formatted.contains('\n') { + 10000 + } else { + formatted.len() + }; + e.insert(formatted); + len + } else { + self.cached_formats[&location].len() + }; + + len + } + } + } + pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize { let mut total = 0; @@ -154,30 +187,7 @@ impl<'a> Writer<'a> { dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2, }; - total += match &attr.attr.value { - ElementAttrValue::AttrLiteral(lit) => ifmt_to_string(lit).len(), - ElementAttrValue::AttrExpr(expr) => expr.span().line_length(), - ElementAttrValue::EventTokens(tokens) => { - let location = Location::new(tokens.span().start()); - - let len = if let std::collections::hash_map::Entry::Vacant(e) = - self.cached_formats.entry(location) - { - let formatted = prettyplease::unparse_expr(tokens); - let len = if formatted.contains('\n') { - 10000 - } else { - formatted.len() - }; - e.insert(formatted); - len - } else { - self.cached_formats[&location].len() - }; - - len - } - }; + total += self.attr_value_len(&attr.attr.value); total += 6; } diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index c83b1059a..bccbc154b 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -791,6 +791,12 @@ impl<'a> IntoAttributeValue<'a> for &'a str { } } +impl<'a> IntoAttributeValue<'a> for String { + fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> { + AttributeValue::Text(bump.alloc(self)) + } +} + impl<'a> IntoAttributeValue<'a> for f64 { fn into_value(self, _: &'a Bump) -> AttributeValue<'a> { AttributeValue::Float(self) diff --git a/packages/rsx/src/attribute.rs b/packages/rsx/src/attribute.rs index a5daafc27..c3bb11516 100644 --- a/packages/rsx/src/attribute.rs +++ b/packages/rsx/src/attribute.rs @@ -4,7 +4,7 @@ use super::*; use proc_macro2::{Span, TokenStream as TokenStream2}; use quote::{quote, ToTokens, TokenStreamExt}; -use syn::{Expr, Ident, LitStr}; +use syn::{parse_quote, Expr, ExprIf, Ident, LitStr}; #[derive(PartialEq, Eq, Clone, Debug, Hash)] pub struct ElementAttrNamed { @@ -58,16 +58,15 @@ impl ToTokens for ElementAttrNamed { let attribute = { match &attr.value { - ElementAttrValue::AttrLiteral(_) | ElementAttrValue::AttrExpr(_) => { + ElementAttrValue::AttrLiteral(_) + | ElementAttrValue::AttrExpr(_) + | ElementAttrValue::AttrOptionalExpr { .. } => { let name = &self.attr.name; let ns = ns(name); let volitile = volitile(name); let attribute = attribute(name); - let value = match &self.attr.value { - ElementAttrValue::AttrLiteral(lit) => quote! { #lit }, - ElementAttrValue::AttrExpr(expr) => quote! { #expr }, - _ => unreachable!(), - }; + let value = &self.attr.value; + let value = quote! { #value }; quote! { __cx.attr( #attribute, @@ -102,13 +101,62 @@ pub struct ElementAttr { pub enum ElementAttrValue { /// attribute: "value" AttrLiteral(IfmtInput), + /// attribute: if bool { "value" } + AttrOptionalExpr { + condition: Expr, + value: Box, + }, /// attribute: true AttrExpr(Expr), /// onclick: move |_| {} EventTokens(Expr), } +impl Parse for ElementAttrValue { + fn parse(input: ParseStream) -> syn::Result { + Ok(if input.peek(Token![if]) { + let if_expr = input.parse::()?; + if is_if_chain_terminated(&if_expr) { + ElementAttrValue::AttrExpr(Expr::If(if_expr)) + } else { + ElementAttrValue::AttrOptionalExpr { + condition: *if_expr.cond, + value: Box::new(syn::parse2(if_expr.then_branch.into_token_stream())?), + } + } + } else if input.peek(LitStr) { + let value = input.parse()?; + ElementAttrValue::AttrLiteral(value) + } else { + let value = input.parse::()?; + ElementAttrValue::AttrExpr(value) + }) + } +} + +impl ToTokens for ElementAttrValue { + fn to_tokens(&self, tokens: &mut TokenStream2) { + match self { + ElementAttrValue::AttrLiteral(lit) => tokens.append_all(quote! { #lit }), + ElementAttrValue::AttrOptionalExpr { condition, value } => { + tokens.append_all(quote! { if #condition { Some(#value) } else { None } }) + } + ElementAttrValue::AttrExpr(expr) => tokens.append_all(quote! { #expr }), + ElementAttrValue::EventTokens(expr) => tokens.append_all(quote! { #expr }), + } + } +} + impl ElementAttrValue { + fn to_str_expr(&self) -> Option { + match self { + ElementAttrValue::AttrLiteral(lit) => Some(quote!(#lit.to_string())), + ElementAttrValue::AttrOptionalExpr { value, .. } => value.to_str_expr(), + ElementAttrValue::AttrExpr(expr) => Some(quote!(#expr.to_string())), + _ => None, + } + } + fn combine(&self, separator: &str, other: &Self) -> Self { match (self, other) { (Self::AttrLiteral(lit1), Self::AttrLiteral(lit2)) => { @@ -134,6 +182,62 @@ impl ElementAttrValue { ifmt.push_expr(expr2.clone()); Self::AttrLiteral(ifmt) } + ( + Self::AttrOptionalExpr { + condition: condition1, + value: value1, + }, + Self::AttrOptionalExpr { + condition: condition2, + value: value2, + }, + ) => { + let first_as_string = value1.to_str_expr(); + let second_as_string = value2.to_str_expr(); + Self::AttrExpr(parse_quote! { + { + let mut __combined = String::new(); + if #condition1 { + __combined.push_str(&#first_as_string); + } + if #condition2 { + if __combined.len() > 0 { + __combined.push_str(&#separator); + } + __combined.push_str(&#second_as_string); + } + __combined + } + }) + } + (Self::AttrOptionalExpr { condition, value }, other) => { + let first_as_string = value.to_str_expr(); + let second_as_string = other.to_str_expr(); + Self::AttrExpr(parse_quote! { + { + let mut __combined = #second_as_string; + if #condition { + __combined.push_str(&#separator); + __combined.push_str(&#first_as_string); + } + __combined + } + }) + } + (other, Self::AttrOptionalExpr { condition, value }) => { + let first_as_string = other.to_str_expr(); + let second_as_string = value.to_str_expr(); + Self::AttrExpr(parse_quote! { + { + let mut __combined = #first_as_string; + if #condition { + __combined.push_str(&#separator); + __combined.push_str(&#second_as_string); + } + __combined + } + }) + } _ => todo!(), } } diff --git a/packages/rsx/src/element.rs b/packages/rsx/src/element.rs index a02669fc7..609205145 100644 --- a/packages/rsx/src/element.rs +++ b/packages/rsx/src/element.rs @@ -11,7 +11,7 @@ use syn::{ parse::{Parse, ParseBuffer, ParseStream}, punctuated::Punctuated, spanned::Spanned, - Expr, Ident, LitStr, Result, Token, + Ident, LitStr, Result, Token, }; // ======================================= @@ -51,13 +51,7 @@ impl Parse for Element { content.parse::()?; - let value = if content.peek(LitStr) { - let value = content.parse()?; - ElementAttrValue::AttrLiteral(value) - } else { - let value = content.parse::()?; - ElementAttrValue::AttrExpr(value) - }; + let value = content.parse::()?; attributes.push(ElementAttrNamed { el_name: el_name.clone(), attr: ElementAttr { @@ -100,23 +94,14 @@ impl Parse for Element { key = Some(content.parse()?); } _ => { - if content.peek(LitStr) { - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr { - name: ElementAttrName::BuiltIn(name), - value: ElementAttrValue::AttrLiteral(content.parse()?), - }, - }); - } else { - attributes.push(ElementAttrNamed { - el_name: el_name.clone(), - attr: ElementAttr { - name: ElementAttrName::BuiltIn(name), - value: ElementAttrValue::AttrExpr(content.parse()?), - }, - }); - } + let value = content.parse::()?; + attributes.push(ElementAttrNamed { + el_name: el_name.clone(), + attr: ElementAttr { + name: ElementAttrName::BuiltIn(name), + value, + }, + }); } } } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 1bda081d9..e1a8aab0f 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -247,7 +247,7 @@ impl Parse for ForLoop { } } -fn is_if_chain_terminated(chain: &ExprIf) -> bool { +pub(crate) fn is_if_chain_terminated(chain: &ExprIf) -> bool { let mut current = chain; loop { if let Some((_, else_block)) = ¤t.else_branch {