Force type headers to format just like Pattern::Apply

This commit is contained in:
Joshua Warner 2024-12-15 10:01:51 -08:00
parent df16e1b633
commit 20aee5c37b
No known key found for this signature in database
GPG key ID: 89AD497003F93FDD
14 changed files with 269 additions and 168 deletions

View file

@ -7,7 +7,7 @@ use crate::expr::{
expr_lift_and_lower, expr_lift_spaces, expr_lift_spaces_after, expr_lift_spaces_before,
fmt_str_literal, is_str_multiline, sub_expr_requests_parens,
};
use crate::pattern::{fmt_pattern, pattern_lift_spaces};
use crate::pattern::{fmt_pattern, pattern_fmt_apply, pattern_lift_spaces};
use crate::pattern::{pattern_lift_spaces_before, starts_with_inline_comment};
use crate::spaces::{
fmt_comments_only, fmt_default_newline, fmt_default_spaces, fmt_spaces, NewlineAt, INDENT,
@ -527,80 +527,16 @@ impl<'a> Formattable for TypeHeader<'a> {
buf: &mut Buf,
_parens: Parens,
_newlines: Newlines,
indent: u16,
) {
buf.indent(indent);
buf.push_str(self.name.value);
let vars_indent = if self.vars.iter().any(|v| v.is_multiline()) {
indent + INDENT
} else {
indent
};
let mut last_after: &[CommentOrNewline<'_>] = &[];
let mut last_multiline = false;
for var in self.vars.iter() {
let var = pattern_lift_spaces(buf.text.bump(), &var.value);
let before = if !last_after.is_empty() {
merge_spaces(buf.text.bump(), last_after, var.before)
} else {
var.before
};
if !before.is_empty() {
if !var.item.is_multiline() {
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, vars_indent)
} else {
fmt_spaces(buf, before.iter(), vars_indent);
}
}
if last_multiline {
buf.ensure_ends_with_newline();
} else {
buf.ensure_ends_with_whitespace();
}
last_after = var.after;
last_multiline = var.item.is_multiline();
let need_parens = matches!(
var.item,
Pattern::Apply(..)
| Pattern::Identifier {
ident: "implements"
}
);
if need_parens {
buf.indent(vars_indent);
buf.push_str("(");
}
fmt_pattern(buf, &var.item, vars_indent, Parens::NotNeeded);
buf.indent(vars_indent);
if need_parens {
buf.push_str(")");
}
}
if !last_after.is_empty() {
if starts_with_inline_comment(last_after.iter()) {
buf.spaces(1);
}
if !last_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, last_after.iter(), indent);
}
}
pattern_fmt_apply(
buf,
Pattern::Tag(self.name.value),
self.vars,
Parens::NotNeeded,
indent,
self.vars.iter().any(|v| v.is_multiline()),
);
}
}

View file

@ -153,103 +153,27 @@ fn fmt_pattern_only(
is_multiline: bool,
) {
match me {
Pattern::Identifier { ident: string } => {
Pattern::Identifier { ident } => {
buf.indent(indent);
snakify_camel_ident(buf, string);
if *ident == "implements" {
buf.push_str("(implements)");
} else {
snakify_camel_ident(buf, ident);
}
}
Pattern::Tag(name) | Pattern::OpaqueRef(name) => {
buf.indent(indent);
buf.push_str(name);
}
Pattern::Apply(loc_pattern, loc_arg_patterns) => {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !loc_arg_patterns.is_empty() && (parens == Parens::InApply);
let indent_more = if is_multiline {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
}
let pat = pattern_lift_spaces(buf.text.bump(), &loc_pattern.value);
if !pat.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, pat.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, pat.before.iter(), indent);
}
}
fmt_pattern_only(&pat.item, buf, Parens::InApply, indent, is_multiline);
let mut last_after = pat.after;
let mut add_newlines = is_multiline;
for loc_arg in loc_arg_patterns.iter() {
buf.spaces(1);
let parens = Parens::InApply;
let arg = pattern_lift_spaces(buf.text.bump(), &loc_arg.value);
let mut was_multiline = arg.item.is_multiline();
let mut before = merge_spaces(buf.text.bump(), last_after, arg.before);
if !before.is_empty() {
if starts_with_block_str(&arg.item) {
// Ick!
// The block string will keep "generating" newlines when formatted (it wants to start on its own line),
// so we strip one out here.
//
// Note that this doesn't affect Expr's because those have explicit parens, and we can control
// whether spaces cross that boundary.
let chop_off = before
.iter()
.rev()
.take_while(|&&s| matches!(s, CommentOrNewline::Newline))
.count();
before = &before[..before.len() - chop_off];
}
if !is_multiline {
was_multiline |= before.iter().any(|s| s.is_comment());
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, indent_more)
} else {
was_multiline |= true;
fmt_spaces(buf, before.iter(), indent_more);
}
}
if add_newlines {
buf.ensure_ends_with_newline();
}
fmt_pattern_only(&arg.item, buf, parens, indent_more, arg.item.is_multiline());
last_after = arg.after;
add_newlines |= was_multiline;
}
if !last_after.is_empty() {
if !is_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent_more)
} else {
fmt_spaces(buf, last_after.iter(), indent_more);
}
}
if parens {
buf.push(')');
}
pattern_fmt_apply(
buf,
loc_pattern.value,
*loc_arg_patterns,
parens,
indent,
is_multiline,
);
}
Pattern::RecordDestructure(loc_patterns) => {
buf.indent(indent);
@ -477,6 +401,104 @@ fn fmt_pattern_only(
}
}
pub fn pattern_fmt_apply(
buf: &mut Buf<'_>,
func: Pattern<'_>,
args: &[Loc<Pattern<'_>>],
parens: Parens,
indent: u16,
is_multiline: bool,
) {
buf.indent(indent);
// Sometimes, an Apply pattern needs parens around it.
// In particular when an Apply's argument is itself an Apply (> 0) arguments
let parens = !args.is_empty() && (parens == Parens::InApply);
let indent_more = if is_multiline {
indent + INDENT
} else {
indent
};
if parens {
buf.push('(');
}
let func = pattern_lift_spaces(buf.text.bump(), &func);
if !func.before.is_empty() {
if !is_multiline {
fmt_comments_only(buf, func.before.iter(), NewlineAt::Bottom, indent)
} else {
fmt_spaces(buf, func.before.iter(), indent);
}
}
fmt_pattern_only(&func.item, buf, Parens::InApply, indent, is_multiline);
let mut last_after = func.after;
let mut add_newlines = is_multiline;
for loc_arg in args.iter() {
buf.spaces(1);
let parens = Parens::InApply;
let arg = pattern_lift_spaces(buf.text.bump(), &loc_arg.value);
let mut was_multiline = arg.item.is_multiline();
let mut before = merge_spaces(buf.text.bump(), last_after, arg.before);
if !before.is_empty() {
if starts_with_block_str(&arg.item) {
// Ick!
// The block string will keep "generating" newlines when formatted (it wants to start on its own line),
// so we strip one out here.
//
// Note that this doesn't affect Expr's because those have explicit parens, and we can control
// whether spaces cross that boundary.
let chop_off = before
.iter()
.rev()
.take_while(|&&s| matches!(s, CommentOrNewline::Newline))
.count();
before = &before[..before.len() - chop_off];
}
if !is_multiline {
was_multiline |= before.iter().any(|s| s.is_comment());
fmt_comments_only(buf, before.iter(), NewlineAt::Bottom, indent_more)
} else {
was_multiline |= true;
fmt_spaces(buf, before.iter(), indent_more);
}
}
if add_newlines {
buf.ensure_ends_with_newline();
}
fmt_pattern_only(&arg.item, buf, parens, indent_more, arg.item.is_multiline());
last_after = arg.after;
add_newlines |= was_multiline;
}
if !last_after.is_empty() {
if !is_multiline {
fmt_comments_only(buf, last_after.iter(), NewlineAt::Bottom, indent_more)
} else {
fmt_spaces(buf, last_after.iter(), indent_more);
}
}
if parens {
buf.push(')');
}
}
pub fn starts_with_inline_comment<'a, I: IntoIterator<Item = &'a CommentOrNewline<'a>>>(
spaces: I,
) -> bool {