mirror of
https://github.com/roc-lang/roc.git
synced 2025-08-04 12:18:19 +00:00
Record Builder formatting
This commit is contained in:
parent
735721769c
commit
c2e50a22b6
4 changed files with 321 additions and 13 deletions
|
@ -5,7 +5,7 @@ use crate::{
|
|||
};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Collection, Expr, ExtractSpaces, HasAbilities, HasAbility, HasClause, HasImpls,
|
||||
Tag, TypeAnnotation, TypeHeader,
|
||||
RecordBuilderField, Tag, TypeAnnotation, TypeHeader,
|
||||
};
|
||||
use roc_parse::ident::UppercaseIdent;
|
||||
use roc_region::all::Loc;
|
||||
|
@ -498,6 +498,109 @@ fn format_assigned_field_help<T>(
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for RecordBuilderField<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
is_multiline_record_builder_field_help(self)
|
||||
}
|
||||
|
||||
fn format_with_options(&self, buf: &mut Buf, _parens: Parens, newlines: Newlines, indent: u16) {
|
||||
// we abuse the `Newlines` type to decide between multiline or single-line layout
|
||||
format_record_builder_field_help(self, buf, indent, 0, newlines == Newlines::Yes);
|
||||
}
|
||||
}
|
||||
|
||||
fn is_multiline_record_builder_field_help(afield: &RecordBuilderField<'_>) -> bool {
|
||||
use self::RecordBuilderField::*;
|
||||
|
||||
match afield {
|
||||
Value(_, spaces, ann) | ApplyValue(_, spaces, ann) => {
|
||||
!spaces.is_empty() || ann.value.is_multiline()
|
||||
}
|
||||
LabelOnly(_) => false,
|
||||
SpaceBefore(_, _) | SpaceAfter(_, _) => true,
|
||||
Malformed(text) => text.chars().any(|c| c == '\n'),
|
||||
}
|
||||
}
|
||||
|
||||
fn format_record_builder_field_help(
|
||||
zelf: &RecordBuilderField,
|
||||
buf: &mut Buf,
|
||||
indent: u16,
|
||||
separator_spaces: usize,
|
||||
is_multiline: bool,
|
||||
) {
|
||||
use self::RecordBuilderField::*;
|
||||
|
||||
match zelf {
|
||||
Value(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
}
|
||||
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.push(':');
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
ApplyValue(name, spaces, ann) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
}
|
||||
|
||||
buf.spaces(separator_spaces);
|
||||
buf.spaces(1);
|
||||
buf.push_str("<-");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
if is_multiline {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(name.value);
|
||||
}
|
||||
SpaceBefore(sub_field, spaces) => {
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
format_record_builder_field_help(
|
||||
sub_field,
|
||||
buf,
|
||||
indent,
|
||||
separator_spaces,
|
||||
is_multiline,
|
||||
);
|
||||
}
|
||||
SpaceAfter(sub_field, spaces) => {
|
||||
format_record_builder_field_help(
|
||||
sub_field,
|
||||
buf,
|
||||
indent,
|
||||
separator_spaces,
|
||||
is_multiline,
|
||||
);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Bottom, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> Formattable for Tag<'a> {
|
||||
fn is_multiline(&self) -> bool {
|
||||
use self::Tag::*;
|
||||
|
|
|
@ -9,7 +9,8 @@ use crate::spaces::{
|
|||
use crate::Buf;
|
||||
use roc_module::called_via::{self, BinOp};
|
||||
use roc_parse::ast::{
|
||||
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern, WhenBranch,
|
||||
AssignedField, Base, Collection, CommentOrNewline, Expr, ExtractSpaces, Pattern,
|
||||
RecordBuilderField, WhenBranch,
|
||||
};
|
||||
use roc_parse::ast::{StrLiteral, StrSegment};
|
||||
use roc_parse::ident::Accessor;
|
||||
|
@ -100,6 +101,7 @@ impl<'a> Formattable for Expr<'a> {
|
|||
Record(fields) => is_collection_multiline(fields),
|
||||
Tuple(fields) => is_collection_multiline(fields),
|
||||
RecordUpdate { fields, .. } => is_collection_multiline(fields),
|
||||
RecordBuilder(fields) => is_collection_multiline(fields),
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -340,10 +342,34 @@ impl<'a> Formattable for Expr<'a> {
|
|||
buf.push_str(string);
|
||||
}
|
||||
Record(fields) => {
|
||||
fmt_record(buf, None, *fields, indent);
|
||||
fmt_record_like(
|
||||
buf,
|
||||
None,
|
||||
*fields,
|
||||
indent,
|
||||
format_assigned_field_multiline,
|
||||
assigned_field_to_space_before,
|
||||
);
|
||||
}
|
||||
RecordUpdate { update, fields } => {
|
||||
fmt_record(buf, Some(*update), *fields, indent);
|
||||
fmt_record_like(
|
||||
buf,
|
||||
Some(*update),
|
||||
*fields,
|
||||
indent,
|
||||
format_assigned_field_multiline,
|
||||
assigned_field_to_space_before,
|
||||
);
|
||||
}
|
||||
RecordBuilder(fields) => {
|
||||
fmt_record_like(
|
||||
buf,
|
||||
None,
|
||||
*fields,
|
||||
indent,
|
||||
format_record_builder_field_multiline,
|
||||
record_builder_field_to_space_before,
|
||||
);
|
||||
}
|
||||
Closure(loc_patterns, loc_ret) => {
|
||||
fmt_closure(buf, loc_patterns, loc_ret, indent);
|
||||
|
@ -1301,12 +1327,18 @@ fn pattern_needs_parens_when_backpassing(pat: &Pattern) -> bool {
|
|||
}
|
||||
}
|
||||
|
||||
fn fmt_record<'a>(
|
||||
fn fmt_record_like<'a, Field, Format, ToSpaceBefore>(
|
||||
buf: &mut Buf,
|
||||
update: Option<&'a Loc<Expr<'a>>>,
|
||||
fields: Collection<'a, Loc<AssignedField<'a, Expr<'a>>>>,
|
||||
fields: Collection<'a, Loc<Field>>,
|
||||
indent: u16,
|
||||
) {
|
||||
format_field_multiline: Format,
|
||||
to_space_before: ToSpaceBefore,
|
||||
) where
|
||||
Field: Formattable,
|
||||
Format: Fn(&mut Buf, &Field, u16, &str),
|
||||
ToSpaceBefore: Fn(&'a Field) -> Option<(&'a Field, &'a [CommentOrNewline<'a>])>,
|
||||
{
|
||||
let loc_fields = fields.items;
|
||||
let final_comments = fields.final_comments();
|
||||
buf.indent(indent);
|
||||
|
@ -1342,7 +1374,7 @@ fn fmt_record<'a>(
|
|||
// In this case, we have to move the comma before the comment.
|
||||
|
||||
let is_first_item = index == 0;
|
||||
if let AssignedField::SpaceBefore(_sub_field, spaces) = &field.value {
|
||||
if let Some((_sub_field, spaces)) = to_space_before(&field.value) {
|
||||
let is_only_newlines = spaces.iter().all(|s| s.is_newline());
|
||||
if !is_first_item
|
||||
&& !is_only_newlines
|
||||
|
@ -1393,7 +1425,7 @@ fn fmt_record<'a>(
|
|||
}
|
||||
}
|
||||
|
||||
fn format_field_multiline<T>(
|
||||
fn format_assigned_field_multiline<T>(
|
||||
buf: &mut Buf,
|
||||
field: &AssignedField<T>,
|
||||
indent: u16,
|
||||
|
@ -1449,7 +1481,7 @@ fn format_field_multiline<T>(
|
|||
// ```
|
||||
// we'd like to preserve this
|
||||
|
||||
format_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
format_assigned_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
}
|
||||
AssignedField::SpaceAfter(sub_field, spaces) => {
|
||||
// We have something like that:
|
||||
|
@ -1463,7 +1495,7 @@ fn format_field_multiline<T>(
|
|||
// # comment
|
||||
// otherfield
|
||||
// ```
|
||||
format_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
format_assigned_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
|
@ -1472,6 +1504,102 @@ fn format_field_multiline<T>(
|
|||
}
|
||||
}
|
||||
|
||||
fn assigned_field_to_space_before<'a, T>(
|
||||
field: &'a AssignedField<'a, T>,
|
||||
) -> Option<(&AssignedField<'a, T>, &'a [CommentOrNewline<'a>])> {
|
||||
match field {
|
||||
AssignedField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn format_record_builder_field_multiline(
|
||||
buf: &mut Buf,
|
||||
field: &RecordBuilderField,
|
||||
indent: u16,
|
||||
separator_prefix: &str,
|
||||
) {
|
||||
use self::RecordBuilderField::*;
|
||||
match field {
|
||||
Value(name, spaces, ann) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(separator_prefix);
|
||||
buf.push_str(":");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
buf.push(',');
|
||||
}
|
||||
ApplyValue(name, spaces, ann) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
|
||||
if !spaces.is_empty() {
|
||||
fmt_spaces(buf, spaces.iter(), indent);
|
||||
buf.indent(indent);
|
||||
}
|
||||
|
||||
buf.push_str(separator_prefix);
|
||||
buf.spaces(1);
|
||||
buf.push_str("<-");
|
||||
buf.spaces(1);
|
||||
ann.value.format(buf, indent);
|
||||
buf.push(',');
|
||||
}
|
||||
LabelOnly(name) => {
|
||||
buf.newline();
|
||||
buf.indent(indent);
|
||||
buf.push_str(name.value);
|
||||
buf.push(',');
|
||||
}
|
||||
SpaceBefore(sub_field, _spaces) => {
|
||||
// We have something like that:
|
||||
// ```
|
||||
// # comment
|
||||
// field,
|
||||
// ```
|
||||
// we'd like to preserve this
|
||||
|
||||
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
}
|
||||
SpaceAfter(sub_field, spaces) => {
|
||||
// We have something like that:
|
||||
// ```
|
||||
// field # comment
|
||||
// , otherfield
|
||||
// ```
|
||||
// we'd like to transform it into:
|
||||
// ```
|
||||
// field,
|
||||
// # comment
|
||||
// otherfield
|
||||
// ```
|
||||
format_record_builder_field_multiline(buf, sub_field, indent, separator_prefix);
|
||||
fmt_comments_only(buf, spaces.iter(), NewlineAt::Top, indent);
|
||||
}
|
||||
Malformed(raw) => {
|
||||
buf.push_str(raw);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn record_builder_field_to_space_before<'a>(
|
||||
field: &'a RecordBuilderField<'a>,
|
||||
) -> Option<(&RecordBuilderField<'a>, &'a [CommentOrNewline<'a>])> {
|
||||
match field {
|
||||
RecordBuilderField::SpaceBefore(sub_field, spaces) => Some((sub_field, spaces)),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn sub_expr_requests_parens(expr: &Expr<'_>) -> bool {
|
||||
match expr {
|
||||
Expr::BinOps(left_side, _) => {
|
||||
|
|
|
@ -4,8 +4,9 @@ use roc_module::called_via::{BinOp, UnaryOp};
|
|||
use roc_parse::{
|
||||
ast::{
|
||||
AbilityMember, AssignedField, Collection, CommentOrNewline, Defs, Expr, Has, HasAbilities,
|
||||
HasAbility, HasClause, HasImpls, Header, Module, Pattern, Spaced, Spaces, StrLiteral,
|
||||
StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef, WhenBranch,
|
||||
HasAbility, HasClause, HasImpls, Header, Module, Pattern, RecordBuilderField, Spaced,
|
||||
Spaces, StrLiteral, StrSegment, Tag, TypeAnnotation, TypeDef, TypeHeader, ValueDef,
|
||||
WhenBranch,
|
||||
},
|
||||
header::{
|
||||
AppHeader, ExposedName, HostedHeader, ImportsEntry, InterfaceHeader, KeywordItem,
|
||||
|
@ -614,6 +615,29 @@ impl<'a, T: RemoveSpaces<'a> + Copy + std::fmt::Debug> RemoveSpaces<'a> for Assi
|
|||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for RecordBuilderField<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
RecordBuilderField::Value(a, _, c) => RecordBuilderField::Value(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
RecordBuilderField::ApplyValue(a, _, c) => RecordBuilderField::ApplyValue(
|
||||
a.remove_spaces(arena),
|
||||
arena.alloc([]),
|
||||
arena.alloc(c.remove_spaces(arena)),
|
||||
),
|
||||
RecordBuilderField::LabelOnly(a) => {
|
||||
RecordBuilderField::LabelOnly(a.remove_spaces(arena))
|
||||
}
|
||||
RecordBuilderField::Malformed(a) => RecordBuilderField::Malformed(a),
|
||||
RecordBuilderField::SpaceBefore(a, _) => a.remove_spaces(arena),
|
||||
RecordBuilderField::SpaceAfter(a, _) => a.remove_spaces(arena),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> RemoveSpaces<'a> for StrLiteral<'a> {
|
||||
fn remove_spaces(&self, arena: &'a Bump) -> Self {
|
||||
match *self {
|
||||
|
@ -660,6 +684,7 @@ impl<'a> RemoveSpaces<'a> for Expr<'a> {
|
|||
fields: fields.remove_spaces(arena),
|
||||
},
|
||||
Expr::Record(a) => Expr::Record(a.remove_spaces(arena)),
|
||||
Expr::RecordBuilder(a) => Expr::RecordBuilder(a.remove_spaces(arena)),
|
||||
Expr::Tuple(a) => Expr::Tuple(a.remove_spaces(arena)),
|
||||
Expr::Var { module_name, ident } => Expr::Var { module_name, ident },
|
||||
Expr::Underscore(a) => Expr::Underscore(a),
|
||||
|
|
|
@ -1926,6 +1926,58 @@ mod test_fmt {
|
|||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn record_builder() {
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{ a: 1, b <- get "b" |> batch, c <- get "c" |> batch, d }
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b <- get "b" |> batch, c <- get "c" |> batch }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b <- get "b" |> batch, c <- get "c" |> batch }
|
||||
"#
|
||||
),
|
||||
);
|
||||
|
||||
expr_formats_same(indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 1,
|
||||
b <- get "b" |> batch,
|
||||
c <- get "c" |> batch,
|
||||
d,
|
||||
}
|
||||
"#
|
||||
));
|
||||
|
||||
expr_formats_to(
|
||||
indoc!(
|
||||
r#"
|
||||
{ a: 1, b <- get "b" |> batch,
|
||||
c <- get "c" |> batch, d }
|
||||
"#
|
||||
),
|
||||
indoc!(
|
||||
r#"
|
||||
{
|
||||
a: 1,
|
||||
b <- get "b" |> batch,
|
||||
c <- get "c" |> batch,
|
||||
d,
|
||||
}
|
||||
"#
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn final_comments_in_records() {
|
||||
expr_formats_same(indoc!(
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue