Răsfoiți Sursa

Add optional attributes

Evan Almloff 1 an în urmă
părinte
comite
d297e2baa2

+ 9 - 0
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))?;
             }

+ 34 - 24
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;
         }

+ 6 - 0
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)

+ 111 - 7
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<ElementAttrValue>,
+    },
     /// attribute: true
     AttrExpr(Expr),
     /// onclick: move |_| {}
     EventTokens(Expr),
 }
 
+impl Parse for ElementAttrValue {
+    fn parse(input: ParseStream) -> syn::Result<Self> {
+        Ok(if input.peek(Token![if]) {
+            let if_expr = input.parse::<ExprIf>()?;
+            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::<Expr>()?;
+            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<TokenStream2> {
+        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!(),
         }
     }

+ 10 - 25
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::<Token![:]>()?;
 
-                let value = if content.peek(LitStr) {
-                    let value = content.parse()?;
-                    ElementAttrValue::AttrLiteral(value)
-                } else {
-                    let value = content.parse::<Expr>()?;
-                    ElementAttrValue::AttrExpr(value)
-                };
+                let value = content.parse::<ElementAttrValue>()?;
                 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::<ElementAttrValue>()?;
+                            attributes.push(ElementAttrNamed {
+                                el_name: el_name.clone(),
+                                attr: ElementAttr {
+                                    name: ElementAttrName::BuiltIn(name),
+                                    value,
+                                },
+                            });
                         }
                     }
                 }

+ 1 - 1
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)) = &current.else_branch {