Przeglądaj źródła

feat: simple support for comments

Jonathan Kelley 3 lat temu
rodzic
commit
a825cdcf58

+ 127 - 17
packages/autofmt/src/buffer.rs

@@ -2,21 +2,14 @@ use std::fmt::{Result, Write};
 
 use dioxus_rsx::BodyNode;
 
+#[derive(Default, Debug)]
 pub struct Buffer {
+    pub src: Vec<String>,
     pub buf: String,
-    pub line: usize,
     pub indent: usize,
 }
 
 impl Buffer {
-    pub fn new() -> Self {
-        Self {
-            buf: String::new(),
-            line: 0,
-            indent: 0,
-        }
-    }
-
     // Create a new line and tab it to the current tab level
     pub fn tabbed_line(&mut self) -> Result {
         self.new_line()?;
@@ -48,17 +41,17 @@ impl Buffer {
         writeln!(self.buf)
     }
 
-    pub fn write_indented_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
-        self.write_ident(lines, node)?;
+    pub fn write_indented_ident(&mut self, node: &BodyNode) -> Result {
+        self.write_ident(node)?;
         Ok(())
     }
 
-    pub fn write_ident(&mut self, lines: &[&str], node: &BodyNode) -> Result {
+    pub fn write_ident(&mut self, node: &BodyNode) -> Result {
         match node {
-            BodyNode::Element(el) => self.write_element(el, lines),
-            BodyNode::Component(component) => self.write_component(component, lines),
+            BodyNode::Element(el) => self.write_element(el),
+            BodyNode::Component(component) => self.write_component(component),
             BodyNode::Text(text) => self.write_text(text),
-            BodyNode::RawExpr(exp) => self.write_raw_expr(exp, lines),
+            BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
         }
     }
 
@@ -67,17 +60,134 @@ impl Buffer {
     }
 
     // Push out the indent level and write each component, line by line
-    pub fn write_body_indented(&mut self, children: &[BodyNode], lines: &[&str]) -> Result {
+    pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
         self.indent += 1;
+
+        let mut comments = Vec::new();
+
         for child in children {
             // Exprs handle their own indenting/line breaks
             if !matches!(child, BodyNode::RawExpr(_)) {
+                // collect all comments upwards
+                let start = child.span().start().line;
+
+                for (id, line) in self.src[..start - 1].iter().enumerate().rev() {
+                    if line.trim().starts_with("//") || line.is_empty() {
+                        comments.push(id);
+                    } else {
+                        break;
+                    }
+                }
+
+                let mut last_was_empty = false;
+                for comment_line in comments.drain(..).rev() {
+                    let line = &self.src[comment_line];
+                    if line.is_empty() {
+                        if !last_was_empty {
+                            self.new_line()?;
+                        }
+                        last_was_empty = true;
+                    } else {
+                        last_was_empty = false;
+                        self.tabbed_line()?;
+                        write!(self.buf, "{}", self.src[comment_line].trim())?;
+                    }
+                }
+
                 self.tabbed_line()?;
             }
 
-            self.write_ident(lines, child)?;
+            self.write_ident(child)?;
         }
+
+        // let mut children_iter = children.iter().enumerate().peekable();
+
+        // while let Some((id, child)) = children_iter.next() {
+        //     let mut written_empty = false;
+
+        //     for line in lines[self.line..child.span().start().line - 1].iter() {
+        //         if id == 0 && line.trim().is_empty() {
+        //             continue;
+        //         }
+
+        //         if !written_empty && line.is_empty() {
+        //             writeln!(self.buf)?;
+        //             written_empty = true;
+        //             continue;
+        //         }
+
+        //         if written_empty && line.is_empty() {
+        //             continue;
+        //         }
+
+        //         writeln!(self.buf)?;
+        //         // self.write_tabs(indent + 1)?;
+        //         write!(self.buf, "{}", line.trim())?;
+        //     }
+
+        //     writeln!(self.buf)?;
+        //     self.indented_tab()?;
+        //     self.write_ident(lines, child)?;
+
+        //     self.line = child.span().end().line;
+        // }
+
+        // for child in children {
+        //     // Exprs handle their own indenting/line breaks
+        //     if !matches!(child, BodyNode::RawExpr(_)) {
+        //         self.tabbed_line()?;
+        //     }
+
+        //     self.write_ident(lines, child)?;
+        // }
         self.indent -= 1;
         Ok(())
     }
 }
+
+// ShortOptimization::PropsOnTop => {
+//     write!(self.buf, " ")?;
+//     self.write_attributes(attributes, true, indent)?;
+
+//     if !children.is_empty() && !attributes.is_empty() {
+//         write!(self.buf, ",")?;
+//     }
+
+//     if let Some(last_attr) = attributes.last() {
+//         self.cur_line = last_attr.name_span().end().line + 1;
+//     }
+
+//     // write the children
+//     for (id, child) in children.iter().enumerate() {
+//         let mut written_empty = false;
+//         for line in self.lines[self.cur_line..child.span().start().line - 1].iter() {
+//             if id == 0 && line.trim().is_empty() {
+//                 continue;
+//             }
+
+//             if !written_empty && line.is_empty() {
+//                 writeln!(self.buf)?;
+//                 written_empty = true;
+//                 continue;
+//             }
+
+//             if written_empty && line.is_empty() {
+//                 continue;
+//             }
+
+//             writeln!(self.buf)?;
+//             // self.write_tabs(indent + 1)?;
+//             write!(self.buf, "{}", line.trim())?;
+//         }
+
+//         writeln!(self.buf)?;
+//         self.write_tabs(indent + 1)?;
+//         self.write_ident(child, indent + 1)?;
+
+//         self.cur_line = child.span().end().line;
+//     }
+
+//     writeln!(self.buf)?;
+//     self.write_tabs(indent)?;
+//     write!(self.buf, "}}")?;
+// }

+ 1 - 2
packages/autofmt/src/component.rs

@@ -13,7 +13,6 @@ impl Buffer {
             manual_props,
             prop_gen_args,
         }: &Component,
-        lines: &[&str],
     ) -> Result {
         let mut name = name.to_token_stream().to_string();
         name.retain(|c| !c.is_whitespace());
@@ -73,7 +72,7 @@ impl Buffer {
         }
 
         for child in children {
-            self.write_indented_ident(lines, child)?;
+            self.write_indented_ident(child)?;
         }
 
         if !body.is_empty() || !children.is_empty() {

+ 34 - 23
packages/autofmt/src/element.rs

@@ -26,7 +26,6 @@ impl Buffer {
             children,
             _is_static,
         }: &Element,
-        lines: &[&str],
     ) -> Result {
         /*
             1. Write the tag
@@ -43,7 +42,7 @@ impl Buffer {
 
         // check if we have a lot of attributes
         let is_short_attr_list = is_short_attrs(attributes);
-        let is_small_children = is_short_children(children);
+        let is_small_children = self.is_short_children(children);
 
         // if we have few attributes and a lot of children, place the attrs on top
         if is_short_attr_list && !is_small_children {
@@ -81,7 +80,7 @@ impl Buffer {
 
                 // write the children
                 for child in children {
-                    self.write_ident(lines, child)?;
+                    self.write_ident(child)?;
                 }
 
                 write!(self.buf, " }}")?;
@@ -96,7 +95,7 @@ impl Buffer {
                 }
 
                 // write the children
-                self.write_body_indented(children, lines)?;
+                self.write_body_indented(children)?;
 
                 self.tabbed_line()?;
                 write!(self.buf, "}}")?;
@@ -108,7 +107,7 @@ impl Buffer {
                 // write the attributes
                 self.write_attributes(attributes, false)?;
 
-                self.write_body_indented(children, lines)?;
+                self.write_body_indented(children)?;
 
                 self.tabbed_line()?;
                 write!(self.buf, "}}")?;
@@ -191,31 +190,43 @@ impl Buffer {
 
         Ok(())
     }
-}
 
-fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool {
-    let total_attr_len = extract_attr_len(attrs);
-    total_attr_len < 80
-}
-
-// check if the children are short enough to be on the same line
-// We don't have the notion of current line depth - each line tries to be < 80 total
-fn is_short_children(children: &[BodyNode]) -> bool {
-    if children.is_empty() {
-        return true;
-    }
+    // check if the children are short enough to be on the same line
+    // We don't have the notion of current line depth - each line tries to be < 80 total
+    fn is_short_children(&self, children: &[BodyNode]) -> bool {
+        if children.is_empty() {
+            // todo: allow elements with comments but no children
+            // like div { /* comment */ }
+            return true;
+        }
 
-    match children {
-        [BodyNode::Text(ref text)] => text.value().len() < 80,
-        [BodyNode::Element(ref el)] => {
-            // && !el.attributes.iter().any(|f| f.attr.is_expr())
+        for child in children {
+            'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
+                if line.trim().starts_with("//") {
+                    return false;
+                } else if line.is_empty() {
+                    continue;
+                } else {
+                    break 'line;
+                }
+            }
+        }
 
-            extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children)
+        match children {
+            [BodyNode::Text(ref text)] => text.value().len() < 80,
+            [BodyNode::Element(ref el)] => {
+                extract_attr_len(&el.attributes) < 80 && self.is_short_children(&el.children)
+            }
+            _ => false,
         }
-        _ => false,
     }
 }
 
+fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool {
+    let total_attr_len = extract_attr_len(attrs);
+    total_attr_len < 80
+}
+
 fn write_key() {
     // if let Some(key) = key.as_ref().map(|f| f.value()) {
     //     if is_long_attr_list {

+ 4 - 3
packages/autofmt/src/expr.rs

@@ -4,7 +4,7 @@ use std::fmt::{self, Result, Write};
 use crate::Buffer;
 
 impl Buffer {
-    pub fn write_raw_expr(&mut self, exp: &syn::Expr, lines: &[&str]) -> Result {
+    pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
         /*
         We want to normalize the expr to the appropriate indent level.
         */
@@ -18,12 +18,13 @@ impl Buffer {
         let end = placement.end();
         let num_spaces_desired = (self.indent * 4) as isize;
 
-        let first = lines[start.line - 1];
+        let first = &self.src[start.line - 1];
+        // let first = lines[start.line - 1];
         let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
 
         let offset = num_spaces_real - num_spaces_desired;
 
-        for line in &lines[start.line - 1..end.line] {
+        for line in &self.src[start.line - 1..end.line] {
             writeln!(self.buf)?;
             // trim the leading whitespace
             if offset < 0 {

+ 4 - 2
packages/autofmt/src/lib.rs

@@ -72,11 +72,13 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
 }
 
 pub fn fmt_block(block: &str) -> Option<String> {
-    let mut buf = Buffer::new();
+    let mut buf = Buffer::default();
+    buf.src = block.lines().map(|f| f.to_string()).collect(); // unnecessary clone, but eh, most files are small
+
     let lines = block.split('\n').collect::<Vec<_>>();
 
     for node in &syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?.roots {
-        buf.write_ident(&lines, node).ok()?;
+        buf.write_ident(&node).ok()?;
     }
 
     Some(buf.buf)

+ 37 - 2
packages/autofmt/tests/sink.rs

@@ -97,7 +97,7 @@ fn print_cases() {
                         }
                     }
                     div { class: "alksdjasd", onclick: move |_| {
-
+                        // hi!
                         liberty!();
                     },
                         div {
@@ -105,7 +105,7 @@ fn print_cases() {
                         }
                     }
 
-                    commented{
+                    commented {
                         // is unparalled
                         class: "asdasd",
 
@@ -113,6 +113,10 @@ fn print_cases() {
                         div {
                             "hi"
                         }
+
+                        div {
+
+                        }
                     }
                 }
         }
@@ -120,6 +124,37 @@ fn print_cases() {
     );
 }
 
+#[test]
+fn format_comments() {
+    let block = r#"
+    div {
+        adsasd: "asd", block: "asd",
+
+
+        // this is a comment
+        "hello"
+
+        // this is a comment 1
+
+        // this is a comment 2
+        "hello"
+
+        div {
+            // this is a comment
+            "hello"
+        }
+
+        div {
+            // empty space
+        }
+    }
+        "#;
+
+    let formatted = fmt_block(block).unwrap();
+
+    println!("{formatted}");
+}
+
 #[test]
 fn formats_component() {
     let block = r#"

+ 11 - 2
packages/rsx/src/node.rs

@@ -1,10 +1,10 @@
 use super::*;
 
-use proc_macro2::TokenStream as TokenStream2;
+use proc_macro2::{Span, TokenStream as TokenStream2};
 use quote::{quote, ToTokens, TokenStreamExt};
 use syn::{
     parse::{Parse, ParseStream},
-    token, Expr, LitStr, Result,
+    token, Expr, LitStr, Result, spanned::Spanned,
 };
 
 /*
@@ -27,6 +27,15 @@ impl BodyNode {
     pub fn is_litstr(&self) -> bool {
         matches!(self, BodyNode::Text(_))
     }
+
+    pub fn span(&self) -> Span {
+        match self {
+            BodyNode::Element(el) => el.name.span(),
+            BodyNode::Component(component) => component.name.span(),
+            BodyNode::Text(text) => text.span(),
+            BodyNode::RawExpr(exp) => exp.span(),
+        }
+    }
 }
 
 impl Parse for BodyNode {