Selaa lähdekoodia

Fix tests for autofmt

Jonathan Kelley 1 vuosi sitten
vanhempi
commit
fa9d92f956

+ 25 - 0
examples/shorthand.rs

@@ -0,0 +1,25 @@
+use dioxus::prelude::*;
+
+fn main() {
+    dioxus_desktop::launch(app);
+}
+
+fn app(cx: Scope) -> Element {
+    let a = 123;
+    let b = 456;
+    let c = 789;
+
+    render! {
+        Component { a, b, c }
+        Component { a, ..ComponentProps { a: 1, b: 2, c: 3 } }
+    }
+}
+
+#[component]
+fn Component(cx: Scope, a: i32, b: i32, c: i32) -> Element {
+    render! {
+        div { "{a}" }
+        div { "{b}" }
+        div { "{c}" }
+    }
+}

+ 4 - 0
packages/autofmt/src/component.rs

@@ -182,6 +182,9 @@ impl Writer<'_> {
                         s.source.as_ref().unwrap().to_token_stream()
                     )?;
                 }
+                ContentField::Shorthand(e) => {
+                    write!(self.out, "{}", e.to_token_stream())?;
+                }
                 ContentField::OnHandlerRaw(exp) => {
                     let out = prettyplease::unparse_expr(exp);
                     let mut lines = out.split('\n').peekable();
@@ -223,6 +226,7 @@ impl Writer<'_> {
             .iter()
             .map(|field| match &field.content {
                 ContentField::Formatted(s) => ifmt_to_string(s).len() ,
+                ContentField::Shorthand(e) => e.to_token_stream().to_string().len(),
                 ContentField::OnHandlerRaw(exp) | ContentField::ManExpr(exp) => {
                     let formatted = prettyplease::unparse_expr(exp);
                     let len = if formatted.contains('\n') {

+ 42 - 2
packages/autofmt/src/writer.rs

@@ -232,8 +232,48 @@ impl<'a> Writer<'a> {
     }
 
     fn write_if_chain(&mut self, ifchain: &IfChain) -> std::fmt::Result {
-        todo!()
-        // self.write_raw_expr(ifchain.span())
+        // Recurse in place by setting the next chain
+        let mut branch = Some(ifchain);
+
+        while let Some(chain) = branch {
+            let IfChain {
+                if_token,
+                cond,
+                then_branch,
+                else_if_branch,
+                else_branch,
+            } = chain;
+
+            write!(
+                self.out,
+                "{} {} {{",
+                if_token.to_token_stream(),
+                prettyplease::unparse_expr(cond)
+            )?;
+
+            self.write_body_indented(then_branch)?;
+
+            if let Some(else_if_branch) = else_if_branch {
+                // write the closing bracket and else
+                self.out.tabbed_line()?;
+                write!(self.out, "}} else ")?;
+
+                branch = Some(else_if_branch);
+            } else if let Some(else_branch) = else_branch {
+                self.out.tabbed_line()?;
+                write!(self.out, "}} else {{")?;
+
+                self.write_body_indented(else_branch)?;
+                branch = None;
+            } else {
+                branch = None;
+            }
+        }
+
+        self.out.tabbed_line()?;
+        write!(self.out, "}}")?;
+
+        Ok(())
     }
 }
 

+ 3 - 3
packages/autofmt/tests/samples/commentshard.rsx

@@ -8,17 +8,17 @@ rsx! {
         "hello world"
 
         // Comments
-        expr1,
+        {expr1},
 
         // Comments
-        expr2,
+        {expr2},
 
         // Comments
         // Comments
         // Comments
         // Comments
         // Comments
-        expr3,
+        {expr3},
 
         div {
             // todo some work in here

+ 4 - 4
packages/autofmt/tests/samples/complex.rsx

@@ -18,7 +18,7 @@ rsx! {
             to: "{to}",
             span { class: "inline-block mr-3", icons::icon_0 {} }
             span { "{name}" }
-            children.is_some().then(|| rsx! {
+            {children.is_some().then(|| rsx! {
                 span {
                     class: "inline-block ml-auto hover:bg-gray-500",
                     onclick: move |evt| {
@@ -27,9 +27,9 @@ rsx! {
                     },
                     icons::icon_8 {}
                 }
-            })
+            })}
         }
-        div { class: "px-4", is_current.then(|| rsx!{ children }) }
+        div { class: "px-4", {is_current.then(|| rsx!{ children })} }
     }
 
     // No nesting
@@ -48,5 +48,5 @@ rsx! {
         }
     }
 
-    div { asdbascasdbasd, asbdasbdabsd, asbdabsdbasdbas }
+    div { "asdbascasdbasd", "asbdasbdabsd", {asbdabsdbasdbas} }
 }

+ 15 - 1
packages/autofmt/tests/samples/ifchain_forloop.rsx

@@ -8,6 +8,20 @@ rsx! {
     // Some ifchain
     if a > 10 {
         //
-        rsx! { div {} }
+        div {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else if a > 20 {
+        h1 {}
+    } else {
+        h3 {}
     }
 }

+ 1 - 1
packages/autofmt/tests/samples/immediate_expr.rsx

@@ -1,4 +1,4 @@
 fn it_works() {
-    cx.render(rsx!(()))
+    cx.render(rsx!({()}))
 }
 

+ 4 - 4
packages/autofmt/tests/samples/long.rsx

@@ -11,7 +11,7 @@ pub fn Explainer<'a>(
     // pt-5 sm:pt-24 lg:pt-24
 
     let mut right = rsx! {
-        div { class: "relative w-1/2", flasher }
+        div { class: "relative w-1/2", {flasher} }
     };
 
     let align = match invert {
@@ -24,7 +24,7 @@ pub fn Explainer<'a>(
             h2 { class: "mb-6 text-3xl leading-tight md:text-4xl md:leading-tight lg:text-3xl lg:leading-tight font-heading font-mono font-bold",
                 "{title}"
             }
-            content
+            {content}
         }
     };
 
@@ -34,8 +34,8 @@ pub fn Explainer<'a>(
 
     cx.render(rsx! {
         div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-            left,
-            right
+            {left},
+            {right}
         }
     })
 }

+ 1 - 1
packages/autofmt/tests/samples/long_exprs.rsx

@@ -6,7 +6,7 @@ rsx! {
                     section { class: "body-font overflow-hidden dark:bg-ideblack",
                         div { class: "container px-6 mx-auto",
                             div { class: "-my-8 divide-y-2 divide-gray-100",
-                                POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })
+                                {POSTS.iter().enumerate().map(|(id, post)| rsx! { BlogPostItem { post: post, id: id } })}
                             }
                         }
                     }

+ 5 - 5
packages/autofmt/tests/samples/multirsx.rsx

@@ -4,20 +4,20 @@ rsx! {
     div {}
 
     // hi
-    div { abcd, ball, s }
+    div { "abcd", "ball", "s" }
 
     //
     //
     //
-    div { abcd, ball, s }
+    div { "abcd", "ball", "s" }
 
     //
     //
     //
     div {
-        abcd,
-        ball,
-        s,
+        "abcd"
+        "ball"
+        "s"
 
         //
         "asdasd"

+ 1 - 1
packages/autofmt/tests/samples/trailing_expr.rsx

@@ -1,7 +1,7 @@
 fn it_works() {
     cx.render(rsx! {
         div {
-            span { "Description: ", package.description.as_deref().unwrap_or("❌❌❌❌ missing") }
+            span { "Description: ", {package.description.as_deref().unwrap_or("❌❌❌❌ missing")} }
         }
     })
 }

+ 2 - 2
packages/autofmt/tests/wrong/multiexpr-4sp.rsx

@@ -1,8 +1,8 @@
 fn ItWroks() {
     cx.render(rsx! {
         div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-            left,
-            right
+            {left},
+            {right}
         }
     })
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-4sp.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
     cx.render(rsx! {
-        div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
+        div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
     })
 }

+ 2 - 2
packages/autofmt/tests/wrong/multiexpr-tab.rsx

@@ -1,8 +1,8 @@
 fn ItWroks() {
 	cx.render(rsx! {
 		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light",
-			left,
-			right
+			{left},
+			{right}
 		}
 	})
 }

+ 1 - 1
packages/autofmt/tests/wrong/multiexpr-tab.wrong.rsx

@@ -1,5 +1,5 @@
 fn ItWroks() {
 	cx.render(rsx! {
-		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", left, right }
+		div { class: "flex flex-wrap items-center dark:text-white py-16 border-t font-light", {left}, {right} }
 	})
 }

+ 172 - 144
packages/rsx/src/component.rs

@@ -19,6 +19,7 @@ use syn::{
     ext::IdentExt,
     parse::{Parse, ParseBuffer, ParseStream},
     spanned::Spanned,
+    token::Brace,
     AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
 };
 
@@ -32,62 +33,15 @@ pub struct Component {
     pub brace: syn::token::Brace,
 }
 
-impl Component {
-    pub fn validate_component_path(path: &syn::Path) -> Result<()> {
-        // ensure path segments doesn't have PathArguments, only the last
-        // segment is allowed to have one.
-        if path
-            .segments
-            .iter()
-            .take(path.segments.len() - 1)
-            .any(|seg| seg.arguments != PathArguments::None)
-        {
-            component_path_cannot_have_arguments!(path.span());
-        }
-
-        // ensure last segment only have value of None or AngleBracketed
-        if !matches!(
-            path.segments.last().unwrap().arguments,
-            PathArguments::None | PathArguments::AngleBracketed(_)
-        ) {
-            invalid_component_path!(path.span());
-        }
-
-        Ok(())
-    }
-
-    pub fn key(&self) -> Option<&IfmtInput> {
-        match self
-            .fields
-            .iter()
-            .find(|f| f.name == "key")
-            .map(|f| &f.content)
-        {
-            Some(ContentField::Formatted(fmt)) => Some(fmt),
-            _ => None,
-        }
-    }
-}
-
 impl Parse for Component {
     fn parse(stream: ParseStream) -> Result<Self> {
         let mut name = stream.parse::<syn::Path>()?;
         Component::validate_component_path(&name)?;
 
         // extract the path arguments from the path into prop_gen_args
-        let prop_gen_args = name.segments.last_mut().and_then(|seg| {
-            if let PathArguments::AngleBracketed(args) = seg.arguments.clone() {
-                seg.arguments = PathArguments::None;
-                Some(args)
-            } else {
-                None
-            }
-        });
+        let prop_gen_args = normalize_path(&mut name);
 
         let content: ParseBuffer;
-
-        // if we see a `{` then we have a block
-        // else parse as a function-like call
         let brace = syn::braced!(content in stream);
 
         let mut fields = Vec::new();
@@ -98,11 +52,25 @@ impl Parse for Component {
             // if we splat into a component then we're merging properties
             if content.peek(Token![..]) {
                 content.parse::<Token![..]>()?;
-                manual_props = Some(content.parse::<Expr>()?);
-            } else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
-                fields.push(content.parse::<ComponentField>()?);
+                manual_props = Some(content.parse()?);
+            } else
+            //
+            // Fields
+            // Named fields
+            if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
+                fields.push(content.parse()?);
+            } else
+            //
+            // shorthand struct initialization
+            // Not a div {}, mod::Component {}, or web-component {}
+            if content.peek(Ident)
+                && !content.peek2(Brace)
+                && !content.peek2(Token![:])
+                && !content.peek2(Token![-])
+            {
+                fields.push(content.parse()?);
             } else {
-                children.push(content.parse::<BodyNode>()?);
+                children.push(content.parse()?);
             }
 
             if content.peek(Token![,]) {
@@ -123,74 +91,23 @@ impl Parse for Component {
 
 impl ToTokens for Component {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
-        let name = &self.name;
-        let prop_gen_args = &self.prop_gen_args;
-
-        let builder = match &self.manual_props {
-            Some(manual_props) => {
-                let mut toks = quote! {
-                    let mut __manual_props = #manual_props;
-                };
-                for field in &self.fields {
-                    if field.name == "key" {
-                        // skip keys
-                    } else {
-                        let name = &field.name;
-                        let val = &field.content;
-                        toks.append_all(quote! {
-                            __manual_props.#name = #val;
-                        });
-                    }
-                }
-                toks.append_all(quote! {
-                    __manual_props
-                });
-                quote! {{
-                    #toks
-                }}
-            }
-            None => {
-                let mut toks = match prop_gen_args {
-                    Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
-                    None => quote! { fc_to_builder(__cx, #name) },
-                };
-                for field in &self.fields {
-                    match field.name.to_string().as_str() {
-                        "key" => {}
-                        _ => toks.append_all(quote! {#field}),
-                    }
-                }
-
-                if !self.children.is_empty() {
-                    let renderer: TemplateRenderer = TemplateRenderer {
-                        roots: &self.children,
-                        location: None,
-                    };
-
-                    toks.append_all(quote! {
-                        .children(
-                            Some({ #renderer })
-                        )
-                    });
-                }
-
-                toks.append_all(quote! {
-                    .build()
-                });
-                toks
-            }
-        };
+        let Self {
+            name,
+            prop_gen_args,
+            ..
+        } = self;
 
-        let fn_name = self.name.segments.last().unwrap().ident.to_string();
+        let builder = self
+            .manual_props
+            .as_ref()
+            .map(|props| self.collect_manual_props(props))
+            .unwrap_or_else(|| self.collect_props());
 
-        let gen_name = match &self.prop_gen_args {
-            Some(gen) => quote! { #name #gen },
-            None => quote! { #name },
-        };
+        let fn_name = self.fn_name();
 
         tokens.append_all(quote! {
             __cx.component(
-                #gen_name,
+                #name #prop_gen_args,
                 #builder,
                 #fn_name
             )
@@ -198,6 +115,91 @@ impl ToTokens for Component {
     }
 }
 
+impl Component {
+    fn validate_component_path(path: &syn::Path) -> Result<()> {
+        // ensure path segments doesn't have PathArguments, only the last
+        // segment is allowed to have one.
+        if path
+            .segments
+            .iter()
+            .take(path.segments.len() - 1)
+            .any(|seg| seg.arguments != PathArguments::None)
+        {
+            component_path_cannot_have_arguments!(path.span());
+        }
+
+        // ensure last segment only have value of None or AngleBracketed
+        if !matches!(
+            path.segments.last().unwrap().arguments,
+            PathArguments::None | PathArguments::AngleBracketed(_)
+        ) {
+            invalid_component_path!(path.span());
+        }
+
+        Ok(())
+    }
+
+    pub fn key(&self) -> Option<&IfmtInput> {
+        match self
+            .fields
+            .iter()
+            .find(|f| f.name == "key")
+            .map(|f| &f.content)
+        {
+            Some(ContentField::Formatted(fmt)) => Some(fmt),
+            _ => None,
+        }
+    }
+
+    fn collect_manual_props(&self, manual_props: &Expr) -> TokenStream2 {
+        let mut toks = quote! { let mut __manual_props = #manual_props; };
+        for field in &self.fields {
+            if field.name == "key" {
+                continue;
+            }
+            let ComponentField { name, content } = field;
+            toks.append_all(quote! { __manual_props.#name = #content; });
+        }
+        toks.append_all(quote! { __manual_props });
+        quote! {{ #toks }}
+    }
+
+    fn collect_props(&self) -> TokenStream2 {
+        let name = &self.name;
+
+        let mut toks = match &self.prop_gen_args {
+            Some(gen_args) => quote! { fc_to_builder(__cx, #name #gen_args) },
+            None => quote! { fc_to_builder(__cx, #name) },
+        };
+        for field in &self.fields {
+            match field.name.to_string().as_str() {
+                "key" => {}
+                _ => toks.append_all(quote! {#field}),
+            }
+        }
+        if !self.children.is_empty() {
+            let renderer: TemplateRenderer = TemplateRenderer {
+                roots: &self.children,
+                location: None,
+            };
+
+            toks.append_all(quote! {
+                .children(
+                    Some({ #renderer })
+                )
+            });
+        }
+        toks.append_all(quote! {
+            .build()
+        });
+        toks
+    }
+
+    fn fn_name(&self) -> String {
+        self.name.segments.last().unwrap().ident.to_string()
+    }
+}
+
 // the struct's fields info
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub struct ComponentField {
@@ -207,21 +209,47 @@ pub struct ComponentField {
 
 #[derive(PartialEq, Eq, Clone, Debug, Hash)]
 pub enum ContentField {
+    Shorthand(Ident),
     ManExpr(Expr),
     Formatted(IfmtInput),
     OnHandlerRaw(Expr),
 }
 
+impl ContentField {
+    fn new_from_name(name: &Ident, input: ParseStream) -> Result<Self> {
+        if name.to_string().starts_with("on") {
+            return Ok(ContentField::OnHandlerRaw(input.parse()?));
+        }
+
+        if *name == "key" {
+            return Ok(ContentField::Formatted(input.parse()?));
+        }
+
+        if input.peek(LitStr) {
+            let forked = input.fork();
+            let t: LitStr = forked.parse()?;
+
+            // the string literal must either be the end of the input or a followed by a comma
+            let res =
+                match (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
+                    true => ContentField::Formatted(input.parse()?),
+                    false => ContentField::ManExpr(input.parse()?),
+                };
+
+            return Ok(res);
+        }
+
+        Ok(ContentField::ManExpr(input.parse()?))
+    }
+}
+
 impl ToTokens for ContentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         match self {
+            ContentField::Shorthand(i) => tokens.append_all(quote! { #i }),
             ContentField::ManExpr(e) => e.to_tokens(tokens),
-            ContentField::Formatted(s) => tokens.append_all(quote! {
-                __cx.raw_text(#s)
-            }),
-            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! {
-                __cx.event_handler(#e)
-            }),
+            ContentField::Formatted(s) => tokens.append_all(quote! { __cx.raw_text(#s) }),
+            ContentField::OnHandlerRaw(e) => tokens.append_all(quote! { __cx.event_handler(#e) }),
         }
     }
 }
@@ -229,30 +257,21 @@ impl ToTokens for ContentField {
 impl Parse for ComponentField {
     fn parse(input: ParseStream) -> Result<Self> {
         let name = Ident::parse_any(input)?;
-        input.parse::<Token![:]>()?;
-
-        let content = {
-            if name.to_string().starts_with("on") {
-                ContentField::OnHandlerRaw(input.parse()?)
-            } else if name == "key" {
-                let content = ContentField::Formatted(input.parse()?);
-                return Ok(Self { name, content });
-            } else if input.peek(LitStr) {
-                let forked = input.fork();
-                let t: LitStr = forked.parse()?;
-                // the string literal must either be the end of the input or a followed by a comma
-                if (forked.is_empty() || forked.peek(Token![,])) && is_literal_foramtted(&t) {
-                    ContentField::Formatted(input.parse()?)
-                } else {
-                    ContentField::ManExpr(input.parse()?)
-                }
-            } else {
-                ContentField::ManExpr(input.parse()?)
-            }
+
+        // if the next token is not a colon, then it's a shorthand field
+        if input.parse::<Token![:]>().is_err() {
+            return Ok(Self {
+                content: ContentField::Shorthand(name.clone()),
+                name,
+            });
         };
+
+        let content = ContentField::new_from_name(&name, input)?;
+
         if input.peek(LitStr) || input.peek(Ident) {
             missing_trailing_comma!(content.span());
         }
+
         Ok(Self { name, content })
     }
 }
@@ -260,9 +279,7 @@ impl Parse for ComponentField {
 impl ToTokens for ComponentField {
     fn to_tokens(&self, tokens: &mut TokenStream2) {
         let ComponentField { name, content, .. } = self;
-        tokens.append_all(quote! {
-            .#name(#content)
-        })
+        tokens.append_all(quote! { .#name(#content) })
     }
 }
 
@@ -281,3 +298,14 @@ fn is_literal_foramtted(lit: &LitStr) -> bool {
 
     false
 }
+
+fn normalize_path(name: &mut syn::Path) -> Option<AngleBracketedGenericArguments> {
+    let seg = name.segments.last_mut()?;
+    match seg.arguments.clone() {
+        PathArguments::AngleBracketed(args) => {
+            seg.arguments = PathArguments::None;
+            Some(args)
+        }
+        _ => None,
+    }
+}

+ 0 - 1
packages/rsx/src/node.rs

@@ -98,7 +98,6 @@ impl Parse for BodyNode {
             // Input::<InputProps<'_, i32> {}
             // crate::Input::<InputProps<'_, i32> {}
             if body_stream.peek(token::Brace) {
-                Component::validate_component_path(&path)?;
                 return Ok(BodyNode::Component(stream.parse()?));
             }
         }