Fixes to autofmt, make it more aggressive (#2230)

* fix: fmt unterminated if, be more aggressive with line splits

* Fix: Handle long exprs in for/if statements
This commit is contained in:
Jonathan Kelley authored and GitHub committed 2024-04-03 15:27:36 -07:00
1 parent 46b0eeb12c
commit be99e29e5f
31 files changed
+464 -117

No files matched your search

+8 -1
View File
@@ -91,8 +91,11 @@ impl Writer<'_> {
write!(self.out, ", ")?;
}
for child in children {
for (id, child) in children.iter().enumerate() {
self.write_ident(child)?;
if id != children.len() - 1 && children.len() > 1 {
write!(self.out, ", ")?;
}
}
write!(self.out, " ")?;
@@ -163,6 +166,10 @@ impl Writer<'_> {
let name = &field.name;
match &field.content {
ContentField::ManExpr(_exp) if field.can_be_shorthand() => {
write!(self.out, "{name}")?;
}
ContentField::ManExpr(exp) => {
let out = unparse_expr(exp);
let mut lines = out.split('\n').peekable();
+38 -64
View File
@@ -71,12 +71,13 @@ impl Writer<'_> {
let children_len = self.is_short_children(children);
let is_small_children = children_len.is_some();
// if we have few attributes and a lot of children, place the attrs on top
// if we have one long attribute and a lot of children, place the attrs on top
if is_short_attr_list && !is_small_children {
opt_level = ShortOptimization::PropsOnTop;
}
// even if the attr is long, it should be put on one line
// However if we have childrne we need to just spread them out for readability
if !is_short_attr_list && attributes.len() <= 1 {
if children.is_empty() {
opt_level = ShortOptimization::Oneliner;
@@ -303,8 +304,12 @@ impl Writer<'_> {
fn write_named_attribute(&mut self, attr: &ElementAttrNamed) -> Result {
self.write_attribute_name(&attr.attr.name)?;
write!(self.out, ": ")?;
self.write_attribute_value(&attr.attr.value)?;
// if the attribute is a shorthand, we don't need to write the colon, just the name
if !attr.attr.can_be_shorthand() {
write!(self.out, ": ")?;
self.write_attribute_value(&attr.attr.value)?;
}
Ok(())
}
@@ -332,10 +337,6 @@ impl Writer<'_> {
beginning.is_empty()
}
pub fn is_empty_children(&self, children: &[BodyNode]) -> bool {
children.is_empty()
}
// 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
// returns the total line length if it's short
@@ -352,11 +353,39 @@ impl Writer<'_> {
return Some(0);
}
// Any comments push us over the limit automatically
if self.children_have_comments(children) {
return None;
}
match children {
[BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()),
// TODO: let rawexprs to be inlined
[BodyNode::RawExpr(ref expr)] => get_expr_length(expr),
// TODO: let rawexprs to be inlined
[BodyNode::Component(ref comp)] if comp.fields.is_empty() => Some(
comp.name
.segments
.iter()
.map(|s| s.ident.to_string().len() + 2)
.sum::<usize>(),
),
// Feedback on discord indicates folks don't like combining multiple children on the same line
// We used to do a lot of math to figure out if we should expand out the line, but folks just
// don't like it.
_ => None,
}
}
fn children_have_comments(&self, children: &[BodyNode]) -> bool {
for child in children {
if self.current_span_is_primary(child.span()) {
'line: for line in self.src[..child.span().start().line - 1].iter().rev() {
match (line.trim().starts_with("//"), line.is_empty()) {
(true, _) => return None,
(true, _) => return true,
(_, true) => continue 'line,
_ => break 'line,
}
@@ -364,62 +393,7 @@ impl Writer<'_> {
}
}
match children {
[BodyNode::Text(ref text)] => Some(ifmt_to_string(text).len()),
[BodyNode::Component(ref comp)] => {
let attr_len = self.field_len(&comp.fields, &comp.manual_props);
if attr_len > 80 {
None
} else if comp.children.is_empty() {
Some(attr_len)
} else {
None
}
}
// TODO: let rawexprs to be inlined
[BodyNode::RawExpr(ref expr)] => get_expr_length(expr),
[BodyNode::Element(ref el)] => {
let attr_len = self.is_short_attrs(&el.attributes);
if el.children.is_empty() && attr_len < 80 {
return Some(el.name.to_string().len());
}
if el.children.len() == 1 {
if let BodyNode::Text(ref text) = el.children[0] {
let value = ifmt_to_string(text);
if value.len() + el.name.to_string().len() + attr_len < 80 {
return Some(value.len() + el.name.to_string().len() + attr_len);
}
}
}
None
}
// todo, allow non-elements to be on the same line
items => {
let mut total_count = 0;
for item in items {
match item {
BodyNode::Component(_) | BodyNode::Element(_) => return None,
BodyNode::Text(text) => {
total_count += ifmt_to_string(text).len();
}
BodyNode::RawExpr(expr) => match get_expr_length(expr) {
Some(len) => total_count += len,
None => return None,
},
BodyNode::ForLoop(_forloop) => return None,
BodyNode::IfChain(_chain) => return None,
}
}
Some(total_count)
}
}
false
}
/// empty everything except for some comments
+7 -14
View File
@@ -141,21 +141,14 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
}
fn write_body(buf: &mut Writer, body: &CallBody) {
let is_short = buf.is_short_children(&body.roots).is_some();
let is_empty = buf.is_empty_children(&body.roots);
if (is_short && !buf.out.indent.split_line_attributes()) || is_empty {
// write all the indents with spaces and commas between
for idx in 0..body.roots.len() - 1 {
let ident = &body.roots[idx];
buf.write_ident(ident).unwrap();
write!(&mut buf.out.buf, ", ").unwrap();
match body.roots.len() {
0 => {}
1 if matches!(body.roots[0], BodyNode::Text(_)) => {
write!(buf.out, " ").unwrap();
buf.write_ident(&body.roots[0]).unwrap();
write!(buf.out, " ").unwrap();
}
// write the last ident without a comma
let ident = &body.roots[body.roots.len() - 1];
buf.write_ident(ident).unwrap();
} else {
buf.write_body_indented(&body.roots).unwrap();
_ => buf.write_body_indented(&body.roots).unwrap(),
}
}
+17 -4
View File
@@ -13,14 +13,20 @@ pub fn unparse_expr(expr: &Expr) -> String {
// Split off the fn main and then cut the tabs off the front
fn unwrapped(raw: String) -> String {
raw.strip_prefix("fn main() {\n")
let mut o = raw
.strip_prefix("fn main() {\n")
.unwrap()
.strip_suffix("}\n")
.unwrap()
.lines()
.map(|line| line.strip_prefix(" ").unwrap()) // todo: set this to tab level
.collect::<Vec<_>>()
.join("\n")
.join("\n");
// remove the semicolon
o.pop();
o
}
fn wrapped(expr: &Expr) -> File {
@@ -31,7 +37,7 @@ fn wrapped(expr: &Expr) -> File {
//
Item::Verbatim(quote::quote! {
fn main() {
#expr
#expr;
}
}),
],
@@ -42,7 +48,7 @@ fn wrapped(expr: &Expr) -> File {
fn unparses_raw() {
let expr = syn::parse_str("1 + 1").unwrap();
let unparsed = unparse(&wrapped(&expr));
assert_eq!(unparsed, "fn main() {\n 1 + 1\n}\n");
assert_eq!(unparsed, "fn main() {\n 1 + 1;\n}\n");
}
#[test]
@@ -52,6 +58,13 @@ fn unparses_completely() {
assert_eq!(unparsed, "1 + 1");
}
#[test]
fn unparses_let_guard() {
let expr = syn::parse_str("let Some(url) = &link.location").unwrap();
let unparsed = unparse_expr(&expr);
assert_eq!(unparsed, "let Some(url) = &link.location");
}
#[test]
fn weird_ifcase() {
let contents = r##"
+43 -9
View File
@@ -188,6 +188,11 @@ impl<'a> Writer<'a> {
pub(crate) fn is_short_attrs(&mut self, attributes: &[AttributeType]) -> usize {
let mut total = 0;
// No more than 3 attributes before breaking the line
if attributes.len() > 3 {
return 100000;
}
for attr in attributes {
if self.current_span_is_primary(attr.start()) {
'line: for line in self.src[..attr.start().start().line - 1].iter().rev() {
@@ -209,7 +214,13 @@ impl<'a> Writer<'a> {
dioxus_rsx::ElementAttrName::Custom(name) => name.value().len() + 2,
};
total += name_len;
total += self.attr_value_len(&attr.attr.value);
//
if attr.attr.value.is_shorthand() {
total += 2;
} else {
total += self.attr_value_len(&attr.attr.value);
}
}
AttributeType::Spread(expr) => {
let expr_len = self.retrieve_formatted_expr(expr).len();
@@ -233,11 +244,12 @@ impl<'a> Writer<'a> {
fn write_for_loop(&mut self, forloop: &ForLoop) -> std::fmt::Result {
write!(
self.out,
"for {} in {} {{",
"for {} in ",
forloop.pat.clone().into_token_stream(),
unparse_expr(&forloop.expr)
)?;
self.write_inline_expr(&forloop.expr)?;
if forloop.body.is_empty() {
write!(self.out, "}}")?;
return Ok(());
@@ -265,12 +277,9 @@ impl<'a> Writer<'a> {
..
} = chain;
write!(
self.out,
"{} {} {{",
if_token.to_token_stream(),
unparse_expr(cond)
)?;
write!(self.out, "{} ", if_token.to_token_stream(),)?;
self.write_inline_expr(cond)?;
self.write_body_indented(then_branch)?;
@@ -296,6 +305,31 @@ impl<'a> Writer<'a> {
Ok(())
}
/// An expression within a for or if block that might need to be spread out across several lines
fn write_inline_expr(&mut self, expr: &Expr) -> std::fmt::Result {
let unparsed = unparse_expr(expr);
let mut lines = unparsed.lines();
let first_line = lines.next().unwrap();
write!(self.out, "{first_line}")?;
let mut was_multiline = false;
for line in lines {
was_multiline = true;
self.out.tabbed_line()?;
write!(self.out, "{line}")?;
}
if was_multiline {
self.out.tabbed_line()?;
write!(self.out, "{{")?;
} else {
write!(self.out, " {{")?;
}
Ok(())
}
}
pub(crate) trait SpanLength {