From a825cdcf58cdb9177102e72c2d5ba28c49546d37 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 28 Jun 2022 15:01:51 -0400 Subject: [PATCH] feat: simple support for comments --- packages/autofmt/src/buffer.rs | 144 ++++++++++++++++++++++++++---- packages/autofmt/src/component.rs | 3 +- packages/autofmt/src/element.rs | 57 +++++++----- packages/autofmt/src/expr.rs | 7 +- packages/autofmt/src/lib.rs | 6 +- packages/autofmt/tests/sink.rs | 39 +++++++- packages/rsx/src/node.rs | 13 ++- 7 files changed, 218 insertions(+), 51 deletions(-) diff --git a/packages/autofmt/src/buffer.rs b/packages/autofmt/src/buffer.rs index e6f021beb..46f3668be 100644 --- a/packages/autofmt/src/buffer.rs +++ b/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, 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, "}}")?; +// } diff --git a/packages/autofmt/src/component.rs b/packages/autofmt/src/component.rs index ec17b4c22..81c75c540 100644 --- a/packages/autofmt/src/component.rs +++ b/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() { diff --git a/packages/autofmt/src/element.rs b/packages/autofmt/src/element.rs index 0180f15a4..247c7dae1 100644 --- a/packages/autofmt/src/element.rs +++ b/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,6 +190,36 @@ impl Buffer { Ok(()) } + + // 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; + } + + 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; + } + } + } + + 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, + } + } } fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { @@ -198,24 +227,6 @@ fn is_short_attrs(attrs: &[ElementAttrNamed]) -> bool { 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; - } - - match children { - [BodyNode::Text(ref text)] => text.value().len() < 80, - [BodyNode::Element(ref el)] => { - // && !el.attributes.iter().any(|f| f.attr.is_expr()) - - extract_attr_len(&el.attributes) < 80 && is_short_children(&el.children) - } - _ => false, - } -} - fn write_key() { // if let Some(key) = key.as_ref().map(|f| f.value()) { // if is_long_attr_list { diff --git a/packages/autofmt/src/expr.rs b/packages/autofmt/src/expr.rs index cf75e7d87..5415f8dd4 100644 --- a/packages/autofmt/src/expr.rs +++ b/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 { diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 6a9e5c594..f59f74bbd 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -72,11 +72,13 @@ pub fn fmt_file(contents: &str) -> Vec { } pub fn fmt_block(block: &str) -> Option { - 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::>(); for node in &syn::parse_str::(block).ok()?.roots { - buf.write_ident(&lines, node).ok()?; + buf.write_ident(&node).ok()?; } Some(buf.buf) diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index c42c5de9d..93d929912 100644 --- a/packages/autofmt/tests/sink.rs +++ b/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#" diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index e8b754f13..73911c15b 100644 --- a/packages/rsx/src/node.rs +++ b/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 {