mirror of
https://github.com/roc-lang/roc.git
synced 2025-09-24 20:42:29 +00:00
Add fuzzing for the formatter and fix bugs
This commit adds fuzzing for the (expr) formatter, with the same invariants that we use for fmt tests: * We start with text, which we parse * We format the AST, which must succeed * We parse back the AST and make sure it's identical igoring whitespace+comments * We format the new AST and assert it's equal to the first formatted version ("idempotency") Interestingly, while a lot of bugs this found were in the formatter, it also found some parsing bugs. It then fixes a bunch of bugs that fell out: * Some small oversights in RemoveSpaces * Make sure `_a` doesn't parse as an inferred type (`_`) followed by an identifier (parsing bug!) * Call `extract_spaces` on a parsed expr before matching on it, lest it be Expr::SpaceBefore - when parsing aliases * A few cases where the formatter generated invalid/different code * Numerous formatting bugs that caused the formatting to not be idempotent The last point there is worth talking further about. There were several cases where the old code was trying to enforce strong opinions about how to insert newlines in function types and defs. In both of those cases, it looked like the goals of (1) idempotency, (2) giving the user some say in the output, and (3) these strong opinions - were often in conflict. For these cases, I erred on the side of following the user's existing choices about where to put newlines. We can go back and re-add this strong opinionation later - but this seemed the right approach for now.
This commit is contained in:
parent
faaa466c70
commit
a046428ce6
57 changed files with 1284 additions and 471 deletions
|
@ -55,13 +55,18 @@ impl<'a> Formattable for Expr<'a> {
|
|||
use roc_parse::ast::StrLiteral::*;
|
||||
|
||||
match literal {
|
||||
PlainLine(_) | Line(_) => {
|
||||
PlainLine(string) => {
|
||||
// When a PlainLine contains '\n' or '"', format as a block string
|
||||
string.contains('"') || string.contains('\n')
|
||||
}
|
||||
Line(_) => {
|
||||
// If this had any newlines, it'd have parsed as Block.
|
||||
false
|
||||
}
|
||||
Block(lines) => {
|
||||
// Block strings don't *have* to be multiline!
|
||||
lines.len() > 1
|
||||
Block(_) => {
|
||||
// Block strings are always formatted on multiple lines,
|
||||
// even if the string is only a single line.
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -358,14 +363,12 @@ impl<'a> Formattable for Expr<'a> {
|
|||
indent,
|
||||
);
|
||||
} else {
|
||||
if !empty_line_before_return {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
// Even if there were no defs, which theoretically should never happen,
|
||||
// still print the return value.
|
||||
ret.format_with_options(buf, Parens::NotNeeded, Newlines::Yes, indent);
|
||||
|
@ -521,11 +524,11 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
|
|||
|
||||
match literal {
|
||||
PlainLine(string) => {
|
||||
buf.indent(indent);
|
||||
buf.push('"');
|
||||
// When a PlainLine contains '\n' or '"', format as a block string
|
||||
if string.contains('"') || string.contains('\n') {
|
||||
buf.push_str("\"\"");
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str("\"\"\"");
|
||||
buf.newline();
|
||||
for line in string.split('\n') {
|
||||
buf.indent(indent);
|
||||
|
@ -533,11 +536,13 @@ pub fn fmt_str_literal<'buf>(buf: &mut Buf<'buf>, literal: StrLiteral, indent: u
|
|||
buf.newline();
|
||||
}
|
||||
buf.indent(indent);
|
||||
buf.push_str("\"\"");
|
||||
buf.push_str("\"\"\"");
|
||||
} else {
|
||||
buf.indent(indent);
|
||||
buf.push('"');
|
||||
buf.push_str_allow_spaces(string);
|
||||
buf.push('"');
|
||||
};
|
||||
buf.push('"');
|
||||
}
|
||||
Line(segments) => {
|
||||
buf.indent(indent);
|
||||
|
@ -684,11 +689,9 @@ fn fmt_when<'a, 'buf>(
|
|||
indent: u16,
|
||||
) {
|
||||
let is_multiline_condition = loc_condition.is_multiline();
|
||||
buf.ensure_ends_with_newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(
|
||||
"\
|
||||
when",
|
||||
);
|
||||
buf.push_str("when");
|
||||
if is_multiline_condition {
|
||||
let condition_indent = indent + INDENT;
|
||||
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue