From 804d9af1c70af010645319c4ec493931cc8d9a48 Mon Sep 17 00:00:00 2001 From: Jonathan Kelley Date: Tue, 26 Apr 2022 18:45:08 -0400 Subject: [PATCH] feat: improve autoformatting algorithm - tons of new tests for various cases to be consistent --- packages/autofmt/src/lib.rs | 146 ++++++++++++++++++----- packages/autofmt/tests/sink.rs | 210 +++++++++++++++++++++++++++++++-- packages/rsx/src/node.rs | 9 ++ 3 files changed, 321 insertions(+), 44 deletions(-) diff --git a/packages/autofmt/src/lib.rs b/packages/autofmt/src/lib.rs index 02dc00a3e..8d4a0cedf 100644 --- a/packages/autofmt/src/lib.rs +++ b/packages/autofmt/src/lib.rs @@ -32,28 +32,36 @@ pub fn get_format_blocks(contents: &str) -> Vec { let mut formatted_blocks = Vec::new(); + let mut last_bracket_end = 0; + for item in matches { let Match { start, end, k } = item; - match cur_match { - Some(ref this_match) => { - // abort nested matches - these get handled automatically - if start < this_match.end { - continue; - } else { - cur_match = Some(item); - } - } - None => { - cur_match = Some(item); - } + if start < last_bracket_end { + continue; } + // match cur_match { + // Some(ref this_match) => { + // // abort nested matches - these get handled automatically + // if start < this_match.end { + // continue; + // } else { + // cur_match = Some(item); + // } + // } + // None => { + // cur_match = Some(item); + // } + // } + let remaining = &contents[end - 1..]; if let Some(bracket_end) = find_bracket_end(remaining) { let sub_string = &contents[end..bracket_end + end - 1]; + last_bracket_end = bracket_end + end - 1; + // with the edge brackets // println!("{}", &contents[end - 1..bracket_end + end]); @@ -65,7 +73,7 @@ pub fn get_format_blocks(contents: &str) -> Vec { writeln!(output).unwrap(); for line in new.lines() { - writeln!(output, "\t{}", line).ok(); + writeln!(output, " {}", line).ok(); } formatted_blocks.push(FormattedBlock { @@ -74,7 +82,11 @@ pub fn get_format_blocks(contents: &str) -> Vec { end: end + bracket_end - 1, }); } + } else { + panic!("failed to format block: {}", sub_string); } + } else { + panic!("failed to find end of block: {}", remaining); } } @@ -105,58 +117,120 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res } = el; write_tabs(buf, indent)?; - writeln!(buf, "{name} {{")?; + write!(buf, "{name} {{")?; + + let total_attr_len = attributes + .iter() + .map(|attr| match &attr.attr { + ElementAttr::AttrText { name, value } => value.value().len(), + ElementAttr::AttrExpression { name, value } => 10, + ElementAttr::CustomAttrText { name, value } => value.value().len(), + ElementAttr::CustomAttrExpression { name, value } => 10, + ElementAttr::EventTokens { name, tokens } => 1000000, + ElementAttr::Meta(_) => todo!(), + }) + .sum::(); + + let is_long_attr_list = total_attr_len > 80; if let Some(key) = key { let key = key.value(); - write_tabs(buf, indent + 1)?; + if is_long_attr_list { + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } write!(buf, "key: \"{key}\"")?; + if !attributes.is_empty() { writeln!(buf, ",")?; } } - for attr in attributes { - write_tabs(buf, indent + 1)?; + let mut attr_iter = attributes.iter().peekable(); + while let Some(attr) = attr_iter.next() { + if is_long_attr_list { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + } else { + write!(buf, " ")?; + } + match &attr.attr { ElementAttr::AttrText { name, value } => { - writeln!(buf, "{name}: \"{value}\",", value = value.value())?; + write!(buf, "{name}: \"{value}\"", value = value.value())?; } ElementAttr::AttrExpression { name, value } => { let out = prettyplease::unparse_expr(value); - writeln!(buf, "{}: {},", name, out)?; + write!(buf, "{}: {}", name, out)?; } - ElementAttr::CustomAttrText { name, value } => todo!(), - ElementAttr::CustomAttrExpression { name, value } => todo!(), + ElementAttr::CustomAttrText { name, value } => { + write!( + buf, + "{name}: \"{value}\"", + name = name.value(), + value = value.value() + )?; + } + + ElementAttr::CustomAttrExpression { name, value } => { + let out = prettyplease::unparse_expr(value); + write!(buf, "{}: {}", name.value(), out)?; + } ElementAttr::EventTokens { name, tokens } => { let out = prettyplease::unparse_expr(tokens); let mut lines = out.split('\n').peekable(); let first = lines.next().unwrap(); - writeln!(buf, "{}: {}", name, first)?; + write!(buf, "{}: {}", name, first)?; while let Some(line) = lines.next() { write_tabs(buf, indent + 1)?; write!(buf, "{}", line)?; - // writeln!(buf, "{}", line)?; + // write!(buf, "{}", line)?; if lines.peek().is_none() { - writeln!(buf, ",")?; - } else { - writeln!(buf)?; + write!(buf, "")?; } } } ElementAttr::Meta(_) => {} } + + if attr_iter.peek().is_some() || !children.is_empty() { + write!(buf, ",")?; + } } - for child in children { - write_ident(buf, child, indent + 1)?; + if children.len() == 1 && children[0].is_litstr() && !is_long_attr_list { + if let BodyNode::Text(text) = &children[0] { + let text_val = text.value(); + if total_attr_len + text_val.len() > 80 { + writeln!(buf)?; + write_tabs(buf, indent + 1)?; + writeln!(buf, "\"{}\"", text.value())?; + write_tabs(buf, indent)?; + } else { + write!(buf, " \"{}\" ", text.value())?; + } + } + } else { + if is_long_attr_list || !children.is_empty() { + writeln!(buf)?; + } + + for child in children { + write_ident(buf, child, indent + 1)?; + } + + if is_long_attr_list || !children.is_empty() { + write_tabs(buf, indent)?; + } else { + write!(buf, " ")?; + } } - write_tabs(buf, indent)?; writeln!(buf, "}}")?; } BodyNode::Component(component) => { @@ -167,10 +241,15 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res manual_props, } = component; - let name = name.to_token_stream().to_string(); + let mut name = name.to_token_stream().to_string(); + name.retain(|c| !c.is_whitespace()); write_tabs(buf, indent)?; - writeln!(buf, "{name} {{")?; + write!(buf, "{name} {{")?; + + if !body.is_empty() || !children.is_empty() { + writeln!(buf)?; + } for field in body { write_tabs(buf, indent + 1)?; @@ -216,7 +295,10 @@ pub fn write_ident(buf: &mut String, node: &BodyNode, indent: usize) -> fmt::Res write_ident(buf, child, indent + 1)?; } - write_tabs(buf, indent)?; + if !body.is_empty() || !children.is_empty() { + write_tabs(buf, indent)?; + } + writeln!(buf, "}}")?; // diff --git a/packages/autofmt/tests/sink.rs b/packages/autofmt/tests/sink.rs index d345b267d..ced3af07b 100644 --- a/packages/autofmt/tests/sink.rs +++ b/packages/autofmt/tests/sink.rs @@ -75,6 +75,103 @@ fn formats_component() { print!("{formatted}"); } +#[test] +fn formats_element() { + let block = r#" + div { + a: "1234567891012345678910123456789101234567891012345678910123456789101234567891012345678910123456789101234567891012345678910", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_short() { + let block = r#" + div { + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + a: "123", + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_nested() { + let block = r#" + h3 { + class: "mb-2 text-xl font-bold", + "Invite Member" + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_nested_no_trailing_tabs() { + let block = r#" + img { class: "mb-6 mx-auto h-24", src: "artemis-assets/images/friends.png", alt: "", } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn formats_element_with_correct_indent() { + let block = r###" + div { + + a { class: "py-2 px-3 bg-indigo-500 hover:bg-indigo-600 rounded text-xs text-white", href: "#", + "Send invitation" + } + } + +"###; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + +#[test] +fn small_elements_and_text_are_small() { + let block = r###" + a { class: " text-white", + "Send invitation" + } +"###; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + #[test] fn formats_component_man_props() { let block = r#" @@ -94,6 +191,18 @@ fn formats_component_man_props() { print!("{formatted}"); } +#[test] +fn formats_component_tiny() { + let block = r#" + Component { a: 123 + } +"#; + + let formatted = fmt_block(block).unwrap(); + + print!("{formatted}"); +} + #[test] fn formats_document() { let block = r#" @@ -114,6 +223,24 @@ rsx!{ print!("{formatted:?}"); } +#[test] +fn component_path_mod_style() { + let block = r#" +rsx!{ + my::thing::Component { + adsasd: "asd", // this is a comment + onclick: move |_| { + let blah = 120; + }, + } +} +"#; + + let formatted = get_format_blocks(block); + + print!("{formatted:?}"); +} + #[test] fn formats_valid_rust_src() { let src = r#" @@ -159,20 +286,79 @@ fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) let block = formatted.into_iter().next().unwrap(); - // println!("{}", &src[block.start..block.end]); - // println!("{}", &src[block.start..block.end - 1]); + src.replace_range( + block.start - 1..block.end + 1, + &format!("{{ {} }}", &block.formatted), + ); +} + +#[test] +fn formats_multiple_blocks() { + let mut src = r#" +#[inline_props] +fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { + const ICON_SIZE: u32 = 36; + + rsx! { + div { + + h1 {"thing"} + + + } + + + } + + rsx! { + div { + + Ball { + a: rsx!{ + "asdasd" + } + } + } + } +} + +#[inline_props] +fn NavItem<'a>(cx: Scope, to: &'static str, children: Element<'a>, icon: Shape) -> Element { + const ICON_SIZE: u32 = 36; + + rsx! { + div { + + h1 {"thing"} + + + } + + + } + + rsx! { + div { + + Ball { + a: rsx!{ + "asdasd" + } + } + } + } +} +"# + .to_string(); + + let formatted = get_format_blocks(&src); + + dbg!(&formatted); + + let block = formatted.into_iter().next().unwrap(); src.replace_range( block.start - 1..block.end + 1, - &format!("{{ {} }}", &block.formatted), - // &format!("{{ {} }}", &block.formatted), + &format!("{{ {} }}", &block.formatted), ); - - println!("{src}"); - - println!("\tthing"); - - // for block in formatted { - // println!("{}", block.formatted); - // } } diff --git a/packages/rsx/src/node.rs b/packages/rsx/src/node.rs index 5eb195716..a41bd7098 100644 --- a/packages/rsx/src/node.rs +++ b/packages/rsx/src/node.rs @@ -23,6 +23,15 @@ pub enum BodyNode { Meta(String), } +impl BodyNode { + pub fn is_litstr(&self) -> bool { + match self { + BodyNode::Text(_) => true, + _ => false, + } + } +} + impl Parse for BodyNode { fn parse(stream: ParseStream) -> Result { if stream.peek(LitStr) {