use crate::ast;
use crate::printer::ArrowChainContext;
use crate::printer::AssumedBreak;
use crate::printer::LeadingGlue;
use crate::printer::ShareTag;
use crate::printer::class_common::ClassHeaderOptions;
use crate::printer::class_common::ClassTypeParamsGap;
use crate::printer::class_expr_has_decorators;
use crate::printer::expressions::assignment::{
is_curried_arrow_chain, is_curried_arrow_chain_that_breaks,
};
use crate::printer::expressions::operators::SeqLayout;
use crate::printer::needs_parens::leftmost_no_lookahead;
use crate::printer::statements::function::FunctionHeadModifier;
use crate::printer::types::helpers::{is_huggable_type, type_literal_members_hold_brace_type};
use crate::printer::{
CommentFilter, CommentSpacing, CommentVec, ParenContext, Printer,
is_multiline_template_expression, unwrap_parenthesized,
};
use smallvec::{SmallVec, smallvec};
use tsv_lang::Span;
use tsv_lang::doc::after_operator::hang_after_operator;
use tsv_lang::doc::arena::{DocArena, DocId};
use tsv_lang::doc::{DocBuf, arena::GroupId};
use tsv_lang::source_scan::has_newline_before_position;
fn arrow_body_always_hugs(expr: &ast::Expression<'_>, source: &str) -> bool {
matches!(
expr.kind,
ast::ExpressionKind::SequenceExpression(_)
| ast::ExpressionKind::ObjectExpression(_)
| ast::ExpressionKind::ArrayExpression(_)
| ast::ExpressionKind::ArrowFunctionExpression(_)
) || (is_multiline_template_expression(expr)
&& !has_newline_before_position(source, expr.span().start))
}
fn leftmost_arrow_body_parens_span(expr: &ast::Expression<'_>) -> Option<Span> {
if matches!(
expr.kind,
ast::ExpressionKind::AssignmentExpression(_) | ast::ExpressionKind::SequenceExpression(_)
) {
return None;
}
let leftmost = leftmost_no_lookahead(expr);
match &leftmost.kind {
ast::ExpressionKind::ObjectExpression(_) => Some(leftmost.span),
ast::ExpressionKind::ClassExpression(c) if class_expr_has_decorators(c) => {
Some(leftmost.span)
}
_ => None,
}
}
fn join_arrow_chain_heads(d: &DocArena, sigs: &[DocId], tails: &[DocId]) -> DocId {
let mut parts = DocBuf::with_capacity(sigs.len().saturating_mul(3).saturating_sub(2));
for (i, &sig) in sigs.iter().enumerate() {
if i > 0 {
parts.push(d.text(" =>"));
parts.push(tails.get(i - 1).copied().unwrap_or_else(|| d.line()));
}
parts.push(sig);
}
d.concat(&parts)
}
struct ArrowGapGluedHead {
resume: u32,
blank_below: bool,
}
#[inline]
pub(in crate::printer) fn prepend_leading(
d: &DocArena,
leading: Option<DocId>,
doc: DocId,
) -> DocId {
match leading {
Some(run) => d.concat(&[run, doc]),
None => doc,
}
}
fn ternary_body_parens_group(d: &DocArena, body: DocId) -> DocId {
d.group(d.concat(&[
d.if_break(d.empty(), d.text("(")),
d.indent(d.concat(&[d.softline(), body])),
d.if_break(d.empty(), d.text(")")),
]))
}
fn ternary_body_parens_group_spaced(d: &DocArena, body: DocId) -> DocId {
d.group(d.concat(&[
d.if_break(d.empty(), d.text(" (")),
d.indent(d.concat(&[d.softline(), body])),
d.if_break(d.empty(), d.text(")")),
]))
}
fn walk_arrow_chain<'a, 'arena>(
head: &'a ast::ArrowFunctionExpression<'arena>,
head_span: Span,
mut visit: impl FnMut(&'a ast::ArrowFunctionExpression<'arena>, Span, Option<u32>),
) -> &'a ast::ArrowFunctionBody<'arena> {
let mut current = head;
let mut current_span = head_span;
let mut gap_start = None;
loop {
visit(current, current_span, gap_start);
gap_start = Some(arrow_token_end(current));
match ¤t.body {
ast::ArrowFunctionBody::Expression(b) => match &b.kind {
ast::ExpressionKind::ArrowFunctionExpression(inner) => {
current = inner;
current_span = b.span;
}
_ => break ¤t.body,
},
ast::ArrowFunctionBody::BlockStatement(_) => break ¤t.body,
}
}
}
pub(in crate::printer) fn has_leftmost_arrow_body_parens(expr: &ast::Expression<'_>) -> bool {
leftmost_arrow_body_parens_span(expr).is_some()
}
fn arrow_body_is_its_own_leftmost_pair(expr: &ast::Expression<'_>) -> bool {
leftmost_arrow_body_parens_span(expr) == Some(expr.span())
}
fn is_huggable_pattern(expr: &ast::Expression<'_>) -> bool {
match &expr.kind {
ast::ExpressionKind::ObjectPattern(_) | ast::ExpressionKind::ArrayPattern(_) => true,
ast::ExpressionKind::AssignmentPattern(ap) => {
matches!(
ap.left.kind,
ast::ExpressionKind::ObjectPattern(_) | ast::ExpressionKind::ArrayPattern(_)
) && match &ap.right.kind {
ast::ExpressionKind::Identifier(_) => true,
ast::ExpressionKind::ObjectExpression(obj) => obj.properties.is_empty(),
ast::ExpressionKind::ArrayExpression(arr) => arr.elements.is_empty(),
_ => false,
}
}
_ => false,
}
}
pub(in crate::printer) fn arrow_token_end(arrow: &ast::ArrowFunctionExpression<'_>) -> u32 {
arrow.arrow_token + "=>".len() as u32
}
fn bare_param_sharing_arrow_start(
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> Option<u32> {
if arrow.params_start.is_some() {
return None;
}
let start = arrow.params.first()?.span().start;
(start == span.start).then_some(start)
}
pub(in crate::printer) fn arrow_signature_has_breaking_comments(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> bool {
let signature_start = arrow.type_parameters.as_ref().map_or_else(
|| arrow.params_start.unwrap_or(span.start),
|type_params| type_params.span.start,
);
if !printer.has_comments_on_page_between(signature_start, arrow.body.span().start) {
return false;
}
let start = if arrow.params.is_empty() {
arrow
.params_start
.and_then(|open| printer.find_closing_paren(open, arrow.arrow_token))
.unwrap_or(arrow.arrow_token)
} else {
signature_start
};
printer.range_has_layout_stable_break_forcing_comment(start, arrow.arrow_token)
|| printer.arrow_trailing_param_comment_forces_break(arrow)
}
pub(in crate::printer) fn function_signature_has_breaking_comments(
printer: &Printer<'_>,
func: &ast::FunctionExpression<'_>,
) -> bool {
if !printer.has_comments_on_page_between(func.params_start, func.body.span.start) {
return false;
}
let end = if func.params.is_empty() {
func.params_start
} else {
func.return_type
.as_ref()
.map_or(func.body.span.start, |return_type| return_type.span.start)
};
printer.range_has_layout_stable_break_forcing_comment(func.params_start, end)
|| printer.function_trailing_param_comment_forces_break(func)
}
fn arrow_signature_will_break(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> bool {
let d = printer.d();
if let Some(return_type) = &arrow.return_type
&& d.will_break(printer.build_arrow_return_type_doc(return_type, arrow.params_start))
{
return true;
}
if arrow.params.is_empty() {
return false;
}
if let Some(type_params) = &arrow.type_parameters
&& d.will_break(printer.build_type_parameter_declaration_doc_wrapping(type_params))
{
return true;
}
let window_start = arrow.params_start.unwrap_or(span.start);
if params_cannot_force_break(printer, arrow.params, window_start, arrow.arrow_token) {
return false;
}
let unforced = if arrow.body.is_expression() {
None
} else {
sole_param_object_type_start(printer, arrow.params, arrow.params_start, || {
printer.arrow_params_end(arrow)
})
};
printer.ask_signature_params(unforced, || {
d.will_break(printer.build_arrow_params_doc_ungrouped(arrow, span))
})
}
fn params_cannot_force_break(
printer: &Printer<'_>,
params: &[ast::Expression<'_>],
start: u32,
end: u32,
) -> bool {
params.iter().all(param_is_flat)
&& !printer.comment_has_newline_between(start, end)
&& !printer.has_comments_on_page_between(start, end)
}
fn param_is_flat(param: &ast::Expression<'_>) -> bool {
match ¶m.kind {
ast::ExpressionKind::Identifier(id) => {
id.decorators().unwrap_or_default().is_empty()
&& annotation_is_flat(id.type_annotation())
}
ast::ExpressionKind::ObjectPattern(pattern) => {
pattern.decorators.unwrap_or_default().is_empty()
&& annotation_is_flat(pattern.type_annotation.as_ref())
&& pattern.properties.iter().all(|property| match property {
ast::ObjectPatternProperty::Property(property) => {
!property.computed
&& !property.method
&& is_bare_identifier(property.key)
&& is_bare_identifier(property.value)
}
ast::ObjectPatternProperty::RestElement(rest) => {
rest.type_annotation.is_none() && is_bare_identifier(rest.argument)
}
})
}
ast::ExpressionKind::ArrayPattern(pattern) => {
pattern.decorators.unwrap_or_default().is_empty()
&& annotation_is_flat(pattern.type_annotation.as_ref())
&& pattern.elements.iter().all(|element| {
element.as_ref().is_none_or(|element| match &element.kind {
ast::ExpressionKind::RestElement(rest) => {
rest.type_annotation.is_none() && is_bare_identifier(rest.argument)
}
_ => is_bare_identifier(element),
})
})
}
ast::ExpressionKind::RestElement(rest) => {
is_bare_identifier(rest.argument) && annotation_is_flat(rest.type_annotation.as_ref())
}
ast::ExpressionKind::AssignmentPattern(default) => {
default.decorators.unwrap_or_default().is_empty()
&& matches!(
&default.left.kind,
ast::ExpressionKind::Identifier(id)
if id.decorators().unwrap_or_default().is_empty()
&& annotation_is_flat(id.type_annotation())
)
&& matches!(
default.right.kind,
ast::ExpressionKind::Literal(_) | ast::ExpressionKind::Identifier(_)
)
}
_ => false,
}
}
fn is_bare_identifier(expr: &ast::Expression<'_>) -> bool {
matches!(
&expr.kind,
ast::ExpressionKind::Identifier(id)
if id.type_annotation().is_none() && id.decorators().unwrap_or_default().is_empty()
)
}
fn annotation_is_flat(annotation: Option<&ast::TSTypeAnnotation<'_>>) -> bool {
annotation.is_none_or(|annotation| match annotation.type_annotation {
ast::TSType::Union(ast::TSUnionType { types, .. })
| ast::TSType::Intersection(ast::TSIntersectionType { types, .. }) => {
types.iter().all(|member| type_is_flat(member))
}
other => type_is_flat(other),
})
}
fn type_is_flat(ty: &ast::TSType<'_>) -> bool {
match ty {
ast::TSType::Keyword(_)
| ast::TSType::ThisType(_)
| ast::TSType::Literal(
ast::TSLiteralType::String(_)
| ast::TSLiteralType::Number(_)
| ast::TSLiteralType::BigInt(_)
| ast::TSLiteralType::UnaryExpression(_),
) => true,
ast::TSType::TypeReference(reference) => reference
.type_arguments
.as_ref()
.is_none_or(|args| args.params.iter().all(|arg| type_is_flat(arg))),
ast::TSType::Array(array) => type_is_flat(array.element_type),
ast::TSType::TypeOperator(operator) => type_is_flat(operator.type_annotation),
ast::TSType::IndexedAccess(access) => {
type_is_flat(access.object_type) && type_is_flat(access.index_type)
}
ast::TSType::TypeQuery(query) => query.type_arguments.is_none(),
_ => false,
}
}
fn sole_param_object_type_start(
printer: &Printer<'_>,
params: &[ast::Expression<'_>],
open: Option<u32>,
close: impl FnOnce() -> Option<u32>,
) -> Option<u32> {
sole_param_object_type(printer, params, open, close).map(|literal| literal.span.start)
}
fn sole_param_object_type<'a>(
printer: &Printer<'_>,
params: &'a [ast::Expression<'a>],
open: Option<u32>,
close: impl FnOnce() -> Option<u32>,
) -> Option<&'a ast::TSTypeLiteral<'a>> {
let [param] = params else {
return None;
};
let annotation = match ¶m.kind {
ast::ExpressionKind::Identifier(id) => id.type_annotation(),
ast::ExpressionKind::ObjectPattern(pattern) => pattern.type_annotation.as_ref(),
ast::ExpressionKind::ArrayPattern(pattern) => pattern.type_annotation.as_ref(),
_ => None,
}?;
let ast::TSType::TypeLiteral(literal) = unwrap_parenthesized(annotation.type_annotation) else {
return None;
};
printer
.hugs_sole_parameter(params, open, close(), true)
.then_some(literal)
}
pub(in crate::printer) struct FlatSoleParamLiteral<'a> {
pub(in crate::printer) literal: u32,
pub(in crate::printer) body: &'a ast::BlockStatement<'a>,
pub(in crate::printer) is_arrow: bool,
pub(in crate::printer) return_break: Option<AssumedBreak>,
}
fn function_return_type_break(func: &ast::FunctionExpression<'_>) -> Option<AssumedBreak> {
let return_type = func.return_type.as_ref()?;
if !crate::printer::types::function_types::type_params_allow_grouping(
func.type_parameters.as_ref(),
) {
return None;
}
match unwrap_parenthesized(return_type.type_annotation) {
ast::TSType::TypeLiteral(literal) => Some(AssumedBreak::TypeLiteral(literal.span.start)),
ast::TSType::Mapped(mapped) => Some(AssumedBreak::MappedType(mapped.span.start)),
_ => None,
}
}
pub(in crate::printer) fn flat_sole_param_literal<'a>(
printer: &Printer<'_>,
arg: &'a ast::Expression<'a>,
) -> Option<FlatSoleParamLiteral<'a>> {
if printer.canonical {
return None;
}
let (literal, body, is_arrow, return_break) = match &arg.kind {
ast::ExpressionKind::ArrowFunctionExpression(arrow) => {
let ast::ArrowFunctionBody::BlockStatement(body) = &arrow.body else {
return None;
};
let literal =
sole_param_object_type(printer, arrow.params, arrow.params_start, || {
printer.arrow_params_end(arrow)
})?;
(literal, body, true, None)
}
ast::ExpressionKind::FunctionExpression(func) => {
let literal =
sole_param_object_type(printer, func.params, Some(func.params_start), || {
printer
.find_closing_paren(func.params_start, func.body.span.start)
.map(|after_paren| after_paren - 1)
})?;
(literal, &func.body, false, function_return_type_break(func))
}
_ => return None,
};
if printer.literal_authored_expanded(literal)
|| type_literal_members_hold_brace_type(literal)
|| printer.type_literal_members_hold_blank_line(literal)
|| printer.has_comments_on_page_between(arg.span.start, body.span.start)
{
return None;
}
Some(FlatSoleParamLiteral {
literal: literal.span.start,
body,
is_arrow,
return_break,
})
}
pub(in crate::printer) fn function_return_hug_break(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
) -> Option<AssumedBreak> {
if printer.canonical {
return None;
}
let ast::ExpressionKind::FunctionExpression(func) = &arg.kind else {
return None;
};
if func.params.len() > 1
|| !crate::printer::types::function_types::type_params_allow_grouping(
func.type_parameters.as_ref(),
)
{
return None;
}
let return_type = func.return_type.as_ref()?.type_annotation;
let brk = return_type_hug_brace(printer, return_type)?;
if crate::printer::is_brace_block_multiline(printer.source, return_type.span())
|| printer.has_comments_on_page_between(arg.span.start, func.body.span.start)
{
return None;
}
Some(brk)
}
fn return_type_hug_brace(printer: &Printer<'_>, ty: &ast::TSType<'_>) -> Option<AssumedBreak> {
let member_brace = |union: &ast::TSUnionType<'_>| {
union
.types
.iter()
.find_map(|member| match unwrap_parenthesized(member) {
ast::TSType::TypeLiteral(literal) if !literal.members.is_empty() => {
Some(AssumedBreak::TypeLiteral(literal.span.start))
}
_ => None,
})
};
match unwrap_parenthesized(ty) {
ast::TSType::TypeLiteral(literal) if !literal.members.is_empty() => {
Some(AssumedBreak::TypeLiteral(literal.span.start))
}
ast::TSType::Mapped(mapped) => Some(AssumedBreak::MappedType(mapped.span.start)),
ast::TSType::TypeReference(reference) => {
let args = reference.type_arguments.as_ref()?;
if let [arg] = args.params {
let peeled = unwrap_parenthesized(arg);
let is_shell = |ty: &ast::TSType<'_>| matches!(ty, ast::TSType::Parenthesized(_));
let shell = is_shell(arg)
|| matches!(peeled, ast::TSType::Union(union)
if union.types.iter().any(|member| is_shell(member)));
if shell {
return match peeled {
ast::TSType::TypeLiteral(literal) if !literal.members.is_empty() => {
Some(AssumedBreak::TypeLiteral(literal.span.start))
}
ast::TSType::Mapped(mapped) => {
Some(AssumedBreak::MappedType(mapped.span.start))
}
ast::TSType::Union(union) if printer.union_prints_hugged(union) => {
member_brace(union)
}
_ => None,
};
}
}
printer.sole_type_arg_hug_break(args)
}
ast::TSType::Union(union) if printer.union_prints_hugged(union) => member_brace(union),
_ => None,
}
}
pub(in crate::printer) fn arrow_signature_refuses_hug(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> bool {
arrow_signature_has_breaking_comments(printer, arrow, span)
|| arrow_signature_will_break(printer, arrow, span)
}
fn function_signature_refuses_hug(
printer: &Printer<'_>,
func: &ast::FunctionExpression<'_>,
arg_count: usize,
) -> bool {
if function_signature_has_breaking_comments(printer, func) {
return true;
}
if func.params.is_empty()
|| !(arg_count > 1 || func.params.iter().all(is_untyped_identifier_param))
{
return false;
}
if params_cannot_force_break(
printer,
func.params,
func.params_start,
func.body.span.start,
) {
return false;
}
let close = printer
.find_closing_paren(func.params_start, func.body.span.start)
.map(|after_paren| after_paren - 1);
let unforced =
sole_param_object_type_start(printer, func.params, Some(func.params_start), || close);
let key = printer.chain_share_key_of(
func,
Span::new(func.params_start, func.body.span.start),
ShareTag::SignatureParamsProbe,
);
printer.ask_signature_params(unforced, || {
printer.d().will_break(printer.chain_shared_doc(key, || {
printer.build_params_doc_with_comments(func.params, Some(func.params_start), close)
}))
})
}
fn is_untyped_identifier_param(param: &ast::Expression<'_>) -> bool {
matches!(¶m.kind, ast::ExpressionKind::Identifier(id) if id.type_annotation().is_none())
}
pub(in crate::printer) fn callback_signature_refuses_hug(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
arg_count: usize,
) -> bool {
match &arg.kind {
ast::ExpressionKind::ArrowFunctionExpression(arrow) => {
arrow_signature_refuses_hug(printer, arrow, arg.span)
}
ast::ExpressionKind::FunctionExpression(func) => {
function_signature_refuses_hug(printer, func, arg_count)
}
_ => false,
}
}
fn has_huggable_type_annotation(expr: &ast::Expression<'_>) -> bool {
match &expr.kind {
ast::ExpressionKind::Identifier(id) => id
.type_annotation()
.is_some_and(|ann| is_huggable_type(ann.type_annotation)),
_ => false,
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(in crate::printer) enum HeadForcedChainLead {
Stacked,
PreservedBlock,
OwnLineRun,
}
impl HeadForcedChainLead {
pub(in crate::printer) fn chain_context(self) -> ArrowChainContext {
match self {
Self::Stacked => ArrowChainContext::AssignmentRhs { leading_run: None },
Self::PreservedBlock | Self::OwnLineRun => ArrowChainContext::None,
}
}
}
impl<'a> Printer<'a> {
fn ask_signature_params(&self, unforced: Option<u32>, ask: impl FnOnce() -> bool) -> bool {
let prev_flat = self.test_call_flat_params.replace(false);
let prev_unforced = self.unforced_param_type_literal.replace(unforced);
let answer = ask();
self.unforced_param_type_literal.set(prev_unforced);
self.test_call_flat_params.set(prev_flat);
answer
}
pub(in crate::printer) fn arrow_empty_params_hold_comments(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
) -> bool {
arrow.params.is_empty()
&& arrow
.params_start
.is_some_and(|open| self.has_comments_on_page_between(open, arrow.arrow_token))
}
pub(in crate::printer) fn param_delimiter_gaps_empty(
&self,
param: &ast::Expression<'_>,
open: Option<u32>,
close: Option<u32>,
comments_present: bool,
) -> bool {
!comments_present
|| (open.is_none_or(|o| !self.has_comments_to_emit_between(o, param.span().start))
&& close.is_none_or(|c| !self.has_comments_to_emit_between(param.span().end, c)))
}
pub(in crate::printer) fn hugs_sole_parameter(
&self,
params: &[ast::Expression<'_>],
open: Option<u32>,
close: Option<u32>,
comments_present: bool,
) -> bool {
params.len() == 1
&& (is_huggable_pattern(¶ms[0]) || has_huggable_type_annotation(¶ms[0]))
&& self.param_delimiter_gaps_empty(¶ms[0], open, close, comments_present)
&& !self.param_has_decorators(¶ms[0])
&& !self.param_annotation_holds_own_line_directive(¶ms[0])
}
fn param_annotation_holds_own_line_directive(&self, param: &ast::Expression<'_>) -> bool {
let ast::ExpressionKind::Identifier(id) = ¶m.kind else {
return false;
};
id.type_annotation().is_some_and(|ann| {
let colon_end = ann.span.start + 1;
self.member_gap_frozen(colon_end, ann.type_annotation.span().start)
|| self
.paren_interior_routed_inner(ann.type_annotation)
.is_some()
})
}
pub(in crate::printer) fn build_with_arrow_chain_context(
&self,
context: ArrowChainContext,
build: impl FnOnce() -> DocId,
) -> DocId {
let prev = self.arrow_chain_context.replace(context);
let doc = build();
self.arrow_chain_context.set(prev);
doc
}
pub(in crate::printer) fn build_led_curried_chain_doc(
&self,
leading_run: Option<DocId>,
gap: (u32, u32),
build: impl FnOnce() -> DocId,
) -> DocId {
let d = self.d();
let unled = ArrowChainContext::AssignmentRhs { leading_run: None };
let Some(run) = leading_run else {
return self.build_with_arrow_chain_context(unled, build);
};
if self.has_multiline_block_comments_on_page_between(gap.0, gap.1) {
let doc = self.build_with_arrow_chain_context(ArrowChainContext::None, build);
return d.concat(&[run, doc]);
}
if !self.comment_run_glued_through(gap.0, gap.1) {
return d.concat(&[run, self.build_with_arrow_chain_context(unled, build)]);
}
let prev = self
.arrow_chain_context
.replace(ArrowChainContext::AssignmentRhs { leading_run });
let doc = build();
match self.arrow_chain_context.replace(prev) {
ArrowChainContext::AssignmentRhs {
leading_run: Some(_),
} => d.concat(&[run, doc]),
_ => doc,
}
}
pub(in crate::printer) fn seam_break_stacks_curried_chain(
&self,
value: &ast::Expression<'_>,
gap_start: u32,
) -> bool {
if !is_curried_arrow_chain(value) {
return false;
}
let value_start = value.span().start;
self.comments_on_page_between(gap_start, value_start)
.next()
.is_some_and(|c| !c.is_block && self.comment_follows_content_on_its_line(c))
|| (self.indentable_block_leads_value(gap_start, value_start)
&& self.comment_run_glued_through(gap_start, value_start))
}
pub(in crate::printer) fn head_forced_chain_lead(
&self,
value: &ast::Expression<'_>,
gap_start: u32,
) -> HeadForcedChainLead {
let value_start = value.span().start;
if !self.has_comments_on_page_between(gap_start, value_start)
|| self.seam_break_stacks_curried_chain(value, gap_start)
{
HeadForcedChainLead::Stacked
} else if !self.comment_run_glued_through(gap_start, value_start) {
HeadForcedChainLead::OwnLineRun
} else if self.has_multiline_block_comments_on_page_between(gap_start, value_start) {
HeadForcedChainLead::PreservedBlock
} else {
HeadForcedChainLead::Stacked
}
}
pub(in crate::printer) fn build_hung_value_doc(
&self,
value: &ast::Expression<'_>,
gap_start: u32,
build: impl FnOnce() -> DocId,
) -> DocId {
if self.seam_break_stacks_curried_chain(value, gap_start) {
self.build_with_arrow_chain_context(ArrowChainContext::AssignmentRhsHung, build)
} else {
build()
}
}
pub(crate) fn build_assignment_value_root_doc(
&self,
expression: &ast::Expression<'_>,
value_gap_start: Option<u32>,
) -> DocId {
self.mark_assignment_value(expression);
let build = || self.build_root_expression_doc(expression);
let Some(gap_start) = value_gap_start.filter(|_| is_curried_arrow_chain(expression)) else {
return build();
};
let value_start = expression.span().start;
let emit_run = || {
self.build_comments_between_filtered_opt(
gap_start,
value_start,
CommentSpacing::Trailing,
CommentFilter::BlockOnly,
)
};
if is_curried_arrow_chain_that_breaks(expression) {
let doc = self.build_with_arrow_chain_context(
ArrowChainContext::AssignmentRhs { leading_run: None },
build,
);
return match emit_run() {
Some(run) => self.d().concat(&[run, doc]),
None => doc,
};
}
self.hoist_owned_value_gap_run(gap_start, expression, false, |hoisted| {
let run = hoisted.or_else(emit_run);
self.build_led_curried_chain_doc(run, (gap_start, value_start), build)
})
.1
}
pub(crate) fn build_stacked_curried_chain_rhs_doc(
&self,
expression: &ast::Expression<'_>,
operator_pos: u32,
build_suffix: impl Fn(DocId) -> DocId,
) -> Option<DocId> {
let gap_start = operator_pos + 1;
if !self.seam_break_stacks_curried_chain(expression, gap_start) {
return None;
}
let d = self.d();
self.mark_assignment_value(expression);
let value_start = expression.span().start;
let hoisted_run = self.hoisted_owned_value_gap_run_opt(gap_start, expression, false);
let hung_value = || {
let value = self.build_hung_value_doc(expression, gap_start, || {
self.build_gap_value_doc(hoisted_run, expression, || {
self.build_root_expression_doc(expression)
})
});
d.concat(&[value, build_suffix(value)])
};
let emit_run = self.build_comments_between_filtered_opt(
gap_start,
value_start,
CommentSpacing::Trailing,
CommentFilter::BlockOnly,
);
if emit_run.is_some()
&& let Some(rhs) = self.broke_after_operator_rhs_doc(gap_start, value_start, hung_value)
{
return Some(d.concat(&[d.text(" ="), rhs]));
}
if let Some(rhs) =
self.build_eq_comment_break_rhs(operator_pos, value_start, " =", hung_value)
{
return Some(rhs);
}
let value = hung_value();
let value = match hoisted_run.or(emit_run) {
Some(run) => d.concat(&[run, value]),
None => value,
};
Some(d.concat(&[d.text(" ="), hang_after_operator(d, value)]))
}
fn build_with_stacked_chain(&self, value: bool, build: impl FnOnce() -> DocId) -> DocId {
let prev = self.in_stacked_arrow_chain.replace(value);
let doc = build();
self.in_stacked_arrow_chain.set(prev);
doc
}
fn build_arrow_doc_wrapping(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
let chain_context = self.arrow_chain_context.replace(ArrowChainContext::None);
if self.should_use_arrow_chain_layout(arrow, span, chain_context) {
return self.build_arrow_chain_doc(arrow, span, chain_context);
}
if matches!(
chain_context,
ArrowChainContext::AssignmentRhs {
leading_run: Some(_)
}
) {
self.arrow_chain_context.set(chain_context);
}
let d = self.d();
let mut parts = DocBuf::new();
let arrow_end = arrow_token_end(arrow);
let sig_doc = self.build_with_stacked_chain(false, || {
self.append_pre_arrow_comments(arrow, span, self.build_arrow_signature_doc(arrow, span))
});
let flat_params = self.expand_last_arg_flat_params.get();
if flat_params && !self.arrow_empty_params_hold_comments(arrow) {
parts.push(d.remove_lines(sig_doc));
} else {
parts.push(d.group(sig_doc));
}
parts.push(d.text(" =>"));
let body_is_arrow = matches!(
&arrow.body,
ast::ArrowFunctionBody::Expression(expr)
if matches!(expr.kind, ast::ExpressionKind::ArrowFunctionExpression(_))
);
let restore_flat = flat_params && !body_is_arrow;
if restore_flat {
self.expand_last_arg_flat_params.set(false);
}
match &arrow.body {
ast::ArrowFunctionBody::Expression(expr) => {
self.build_arrow_expression_body(&mut parts, expr, arrow, span, arrow_end);
}
ast::ArrowFunctionBody::BlockStatement(block) => {
self.build_arrow_block_body(&mut parts, block, arrow_end);
}
}
if restore_flat {
self.expand_last_arg_flat_params.set(true);
}
d.concat(&parts)
}
fn arrow_body_shell_holds_comments(
&self,
expr: &ast::Expression<'_>,
arrow_span_end: u32,
frozen: Option<Span>,
) -> bool {
let gap_start = self.shell_trailing_gap_start(expr, arrow_span_end, frozen, false);
self.has_trailing_paren_comments(gap_start, arrow_span_end)
}
fn build_arrow_expression_body(
&self,
parts: &mut DocBuf,
expr: &ast::Expression<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
arrow_span: Span,
arrow_end: u32,
) {
let d = self.d();
let frozen = self.value_head_frozen_span(arrow_end, expr.span());
let has_trailing_paren_comments =
self.arrow_body_shell_holds_comments(expr, arrow_span.end, frozen);
if has_trailing_paren_comments {
let body_start = expr.span().start;
let body_doc = match frozen {
Some(frozen) => self.build_frozen_kept_paren_doc(frozen, arrow_span.end),
None => self.build_expression_doc_keep_paren_comments(
expr,
arrow_span.end,
SeqLayout::Hanging,
),
};
if self.has_own_line_post_arrow_comment(arrow_end, body_start) {
self.push_arrow_gap_tail(parts, arrow_end, body_start, |resume| {
prepend_leading(d, self.arrow_gap_leading_run(resume, body_start), body_doc)
});
} else {
parts.push(d.text(" "));
if let Some(run) = self.build_rhs_comments_glued_opt(arrow_end, body_start) {
parts.push(run);
}
parts.push(body_doc);
}
return;
}
let body_start = expr.span().start;
let has_post_arrow_comments = self.has_comments_to_emit_between(arrow_end, body_start);
let has_own_line_comment =
has_post_arrow_comments && self.has_own_line_post_arrow_comment(arrow_end, body_start);
debug_assert!(
frozen.is_none() || has_own_line_comment,
"an =>→body freeze must reach the own-line-comment arm"
);
let is_arrow_body = matches!(expr.kind, ast::ExpressionKind::ArrowFunctionExpression(_))
&& !self.relational_sibling_pair_is_kept(expr);
let chain_should_break = is_arrow_body
&& !self.skip_arrow_chain.get()
&& (self.in_stacked_arrow_chain.get()
|| crate::printer::arrow_chain_should_break(arrow));
let body_arrow_param_comment_forces_break = matches!(
&expr.kind,
ast::ExpressionKind::ArrowFunctionExpression(body_arrow)
if self.arrow_trailing_param_comment_forces_break(body_arrow)
);
let should_hug = !has_own_line_comment
&& self.arrow_body_hugs(expr)
&& !chain_should_break
&& !body_arrow_param_comment_forces_break;
let hoisted_run = self
.arrow_body_inject
.get()
.is_none_or(|(span, _)| span != body_start)
.then(|| {
self.hoisted_owned_value_gap_run_opt(
arrow_end,
expr,
self.needs_parens(expr, ParenContext::ArrowBody),
)
})
.flatten();
let build_body = |build: &dyn Fn() -> DocId| -> DocId {
self.build_value_under_hoist(hoisted_run, expr, build)
};
let gap_run = || {
hoisted_run.or_else(|| {
has_post_arrow_comments
.then(|| self.build_rhs_comments_glued_opt(arrow_end, body_start))
.flatten()
})
};
if has_own_line_comment {
self.push_arrow_gap_tail(parts, arrow_end, body_start, |resume| {
self.build_arrow_body_with_comments_doc(expr, resume, body_start, frozen)
});
} else if should_hug {
parts.push(self.arrow_hug_body_doc(
arrow,
arrow_span,
arrow_end,
expr,
hoisted_run,
gap_run(),
));
} else if is_arrow_body && (chain_should_break || self.in_stacked_arrow_chain.get()) {
let body_doc = build_body(&|| {
self.build_with_stacked_chain(true, || {
self.build_arrow_body_doc_with_leading(expr, gap_run())
})
});
parts.push(d.concat(&[d.hardline(), body_doc]));
} else if is_arrow_body && body_arrow_param_comment_forces_break {
let body_doc = build_body(&|| {
self.build_with_stacked_chain(true, || {
self.build_arrow_body_doc_with_leading(expr, gap_run())
})
});
parts.push(d.indent_hardline(body_doc));
} else if self.in_stacked_arrow_chain.get() {
let body_doc = build_body(&|| {
self.build_with_stacked_chain(false, || {
self.build_arrow_body_doc_with_leading(expr, gap_run())
})
});
parts.push(d.indent_hardline(body_doc));
} else if self.arrow_body_takes_ternary_pair(expr) {
let body_doc = if let Some((span, doc)) = self.arrow_body_inject.get()
&& span == expr.span().start
{
doc
} else {
build_body(&|| self.build_expression_doc_claiming_outermost(expr))
};
let soft_run = self.value_gap_soft_broke_after_run(arrow_end, body_start);
if let Some(run) = &soft_run
&& self.broke_after_run_break_is_forced(run, body_doc)
{
parts.push(self.break_after_operator_run_doc(run, body_start, body_doc));
} else {
let broke_after = soft_run.is_some();
let with_leading = if let Some(run) = soft_run {
let mut run_parts = DocBuf::new();
self.push_leading_run_with_soft_line(&mut run_parts, &run);
run_parts.push(body_doc);
d.concat(&run_parts)
} else {
prepend_leading(d, gap_run(), body_doc)
};
if broke_after {
parts.push(ternary_body_parens_group_spaced(d, with_leading));
} else {
parts.push(d.text(" "));
parts.push(if d.will_break(body_doc) {
hang_after_operator(d, with_leading)
} else {
ternary_body_parens_group(d, with_leading)
});
}
}
} else {
let body_doc = build_body(&|| self.build_arrow_body_doc(expr));
parts.push(
if let Some(run) = self.value_gap_soft_broke_after_run(arrow_end, body_start) {
self.break_or_hang_after_operator_run_doc(&run, body_start, body_doc)
} else {
hang_after_operator(d, prepend_leading(d, gap_run(), body_doc))
},
);
}
}
fn arrow_hug_body_doc(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
arrow_span: Span,
gap_start: u32,
expr: &ast::Expression<'_>,
hoisted_run: Option<DocId>,
gap_run: Option<DocId>,
) -> DocId {
let d = self.d();
let body_start = expr.span().start;
let broken = self
.arrow_gap_broke_after_run(arrow, arrow_span, gap_start, body_start)
.and_then(|run| {
let body_doc = self.build_arrow_body_doc(expr);
d.will_break(body_doc)
.then(|| self.break_after_operator_run_doc(&run, body_start, body_doc))
});
if let Some(doc) = broken {
return doc;
}
let mut parts: DocBuf = smallvec![d.text(" ")];
if let Some(run) = gap_run {
parts.push(run);
}
parts.push(
self.build_value_under_hoist(hoisted_run, expr, || self.build_arrow_body_doc(expr)),
);
d.concat(&parts)
}
pub(crate) fn arrow_block_body_doc(&self, block: &ast::BlockStatement<'_>) -> DocId {
if let Some((span, doc)) = self.arrow_body_inject.get()
&& span == block.span.start
{
return doc;
}
self.build_with_stacked_chain(false, || self.build_block_statement_doc(block))
}
fn build_arrow_block_body(
&self,
parts: &mut DocBuf,
block: &ast::BlockStatement<'_>,
arrow_end: u32,
) {
let d = self.d();
let body_start = block.span.start;
let has_post_arrow_comments = self.has_comments_to_emit_between(arrow_end, body_start);
let block_doc = match self.gap_frozen_span(arrow_end, block.span) {
Some(frozen) => self.build_frozen_node_doc(frozen),
None => self.arrow_block_body_doc(block),
};
if has_post_arrow_comments && self.has_own_line_post_arrow_comment(arrow_end, body_start) {
self.push_arrow_gap_tail(parts, arrow_end, body_start, |resume| {
prepend_leading(d, self.arrow_gap_leading_run(resume, body_start), block_doc)
});
return;
}
if has_post_arrow_comments {
let mut comment_parts: DocBuf = DocBuf::new();
for comment in self.comments_to_emit_between(arrow_end, body_start) {
comment_parts.push(d.text(" "));
comment_parts.push(self.build_comment_doc(comment));
}
parts.push(d.concat(&comment_parts));
}
parts.push(d.text(" "));
parts.push(block_doc);
}
fn should_use_arrow_chain_layout(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
context: ArrowChainContext,
) -> bool {
if self.skip_arrow_chain.get() {
return false;
}
if context == ArrowChainContext::None && self.in_stacked_arrow_chain.get() {
return false;
}
let body_is_arrow = matches!(
&arrow.body,
ast::ArrowFunctionBody::Expression(b)
if matches!(b.kind, ast::ExpressionKind::ArrowFunctionExpression(_))
);
if !body_is_arrow || self.chain_holds_region_kept_body(arrow) {
return false;
}
if matches!(
context,
ArrowChainContext::AssignmentRhs { .. } | ArrowChainContext::AssignmentRhsHung
) && crate::printer::arrow_chain_should_break(arrow)
{
return false;
}
!self.chain_comment_outside_emitted_regions(arrow, span)
}
fn chain_holds_region_kept_body(&self, arrow: &ast::ArrowFunctionExpression<'_>) -> bool {
if !self.has_relational_sibling_pairs.get() {
return false;
}
let mut current = arrow;
while let ast::ArrowFunctionBody::Expression(body) = ¤t.body
&& let ast::ExpressionKind::ArrowFunctionExpression(inner) = &body.kind
{
if self.relational_sibling_pair_is_kept(body) {
return true;
}
current = inner;
}
false
}
fn chain_comment_outside_emitted_regions(
&self,
head: &ast::ArrowFunctionExpression<'_>,
head_span: Span,
) -> bool {
if !self.has_comments_to_emit_between(head_span.start, head_span.end) {
return false;
}
let mut emitted: SmallVec<[(u32, u32); 8]> = SmallVec::new();
let mut terminal_gap_start = head_span.start;
let terminal = walk_arrow_chain(head, head_span, |current, current_span, gap_start| {
emitted.push((
current_span.start,
self.arrow_signature_end(current, current_span),
));
if let Some(gap_start) = gap_start {
emitted.push((gap_start, current_span.start));
}
terminal_gap_start = arrow_token_end(current);
});
let body = terminal.span();
if self.terminal_arrow_gap_emitted(terminal_gap_start, body) {
emitted.push((terminal_gap_start, body.start));
}
let printed_end = match terminal {
ast::ArrowFunctionBody::Expression(expr) => {
self.shell_trailing_gap_start(expr, head_span.end, None, false)
}
ast::ArrowFunctionBody::BlockStatement(_) => body.end,
};
emitted.push((body.start, printed_end));
self.comments_to_emit_between(head_span.start, head_span.end)
.any(|comment| {
!emitted
.iter()
.any(|&(start, end)| comment.span.start >= start && comment.span.end <= end)
})
}
fn terminal_arrow_gap_emitted(&self, gap_start: u32, body_span: Span) -> bool {
self.value_head_frozen_span(gap_start, body_span).is_none()
}
fn arrow_gap_leading_run(&self, start: u32, end: u32) -> Option<DocId> {
self.build_rhs_comments_opt(start, end)
}
fn push_arrow_gap_glued_head(
&self,
parts: &mut DocBuf,
sig_end: u32,
body_start: u32,
) -> Option<ArrowGapGluedHead> {
let d = self.d();
let glued: CommentVec<'_> = self
.comments_to_emit_between(sig_end, body_start)
.take_while(|c| self.is_same_line(sig_end, c.span.start))
.collect();
if glued.last().is_none_or(|c| c.is_block) {
return None;
}
let mut resume = sig_end;
for comment in &glued {
if comment.is_block {
parts.push(d.text(" "));
parts.push(self.build_comment_doc(comment));
} else {
parts.push(self.build_trailing_line_comment_doc(comment));
}
resume = comment.span.end;
}
let next = self.blank_scan_end(resume, body_start);
Some(ArrowGapGluedHead {
resume,
blank_below: self.has_blank_line_between_strict(resume, next),
})
}
fn arrow_glued_head_break(&self, head: &ArrowGapGluedHead) -> DocId {
let d = self.d();
if head.blank_below {
d.concat(&[d.literalline(), d.hardline()])
} else {
d.hardline()
}
}
fn push_arrow_gap_tail(
&self,
parts: &mut DocBuf,
sig_end: u32,
body_start: u32,
build_tail: impl FnOnce(u32) -> DocId,
) {
let d = self.d();
let head = self.push_arrow_gap_glued_head(parts, sig_end, body_start);
let tail = build_tail(head.as_ref().map_or(sig_end, |h| h.resume));
parts.push(match head {
Some(head) => d.indent(d.concat(&[self.arrow_glued_head_break(&head), tail])),
None => hang_after_operator(d, tail),
});
}
fn build_arrow_chain_doc(
&self,
head: &ast::ArrowFunctionExpression<'_>,
span: Span,
context: ArrowChainContext,
) -> DocId {
let d = self.d();
let mut sig_docs: DocBuf = DocBuf::new();
let mut gap_tails: DocBuf = DocBuf::new();
let mut terminal_gap_start = span.start;
let mut terminal_arrow = (head, span);
let terminal = walk_arrow_chain(head, span, |current, current_span, gap_start| {
let sig = d.group(self.build_arrow_signature_doc(current, current_span));
let sig = match gap_start {
None => sig,
Some(gap_start) => {
let sig = self.prepend_owned_leading_comment_at(current_span.start, sig);
let mut glued: DocBuf = DocBuf::new();
let head =
self.push_arrow_gap_glued_head(&mut glued, gap_start, current_span.start);
let resume = head.as_ref().map_or(gap_start, |h| h.resume);
gap_tails.push(match head {
Some(head) => {
glued.push(self.arrow_glued_head_break(&head));
d.concat(&glued)
}
None => d.line(),
});
prepend_leading(
d,
self.arrow_gap_leading_run(resume, current_span.start),
sig,
)
}
};
sig_docs.push(sig);
terminal_gap_start = arrow_token_end(current);
terminal_arrow = (current, current_span);
});
let should_break_chain = crate::printer::arrow_chain_should_break(head);
let keyed = |id: GroupId| (id.doc(), Some(id));
let (heads, heads_id) = match context {
ArrowChainContext::AssignmentRhs { leading_run } => {
let inner = d.group(join_arrow_chain_heads(d, &sig_docs, &gap_tails));
let led = match leading_run {
Some(run) => d.concat(&[d.softline(), run, inner]),
None => d.concat(&[d.softline(), inner]),
};
keyed(d.group_with_id(d.indent(led)))
}
ArrowChainContext::AssignmentRhsHung => (
d.group(join_arrow_chain_heads(d, &sig_docs, &gap_tails)),
None,
),
ArrowChainContext::Callee => {
let joined = join_arrow_chain_heads(d, &sig_docs, &gap_tails);
let inner = if should_break_chain {
d.group_break(joined)
} else {
d.group(joined)
};
keyed(d.group_with_id_break(
d.indent(d.concat(&[d.softline(), inner])),
!self.arrow_chain_body_stays_on_head_line(
terminal,
terminal_gap_start,
should_break_chain,
),
))
}
ArrowChainContext::CallArgOrBinaryish | ArrowChainContext::None => {
let heads_doc = match sig_docs.split_first() {
Some((&sig0, rest)) => {
let first_tail = gap_tails.first().copied().unwrap_or_else(|| d.line());
let rest_joined =
join_arrow_chain_heads(d, rest, gap_tails.get(1..).unwrap_or(&[]));
d.concat(&[
sig0,
d.text(" =>"),
d.indent(d.concat(&[first_tail, rest_joined])),
])
}
None => join_arrow_chain_heads(d, &sig_docs, &gap_tails),
};
keyed(d.group_with_id_break(heads_doc, should_break_chain))
}
};
let body_part = match terminal {
ast::ArrowFunctionBody::Expression(expr) => {
let hoisted_run = self
.arrow_body_inject
.get()
.is_none_or(|(span, _)| span != expr.span().start)
.then(|| {
self.hoisted_owned_value_gap_run_opt(
terminal_gap_start,
expr,
self.needs_parens(expr, ParenContext::ArrowBody),
)
})
.flatten();
let glued_run = hoisted_run.or_else(|| {
self.build_rhs_comments_glued_opt(terminal_gap_start, expr.span().start)
});
let broke_run: Option<CommentVec<'a>> =
self.value_gap_soft_broke_after_run(terminal_gap_start, expr.span().start);
let terminal_run = match &broke_run {
Some(run) => {
let mut run_parts = DocBuf::new();
self.push_leading_run_with_soft_line(&mut run_parts, run);
Some(d.concat(&run_parts))
}
None => glued_run,
};
let build_body = |build: &dyn Fn() -> DocId| -> DocId {
self.build_value_under_hoist(hoisted_run, expr, build)
};
if self.has_own_line_post_arrow_comment(terminal_gap_start, expr.span().start) {
let mut gap_parts = DocBuf::new();
self.push_arrow_gap_tail(
&mut gap_parts,
terminal_gap_start,
expr.span().start,
|resume| {
build_body(&|| {
self.build_arrow_body_with_comments_doc(
expr,
resume,
expr.span().start,
None,
)
})
},
);
d.concat(&gap_parts)
} else if self.arrow_body_hugs(expr) {
let (arrow, arrow_span) = terminal_arrow;
self.arrow_hug_body_doc(
arrow,
arrow_span,
terminal_gap_start,
expr,
hoisted_run,
glued_run,
)
} else if self.arrow_body_takes_ternary_pair(expr) && !should_break_chain {
let raw_body =
build_body(&|| self.build_expression_doc_claiming_outermost(expr));
match broke_run
.as_ref()
.filter(|run| self.broke_after_run_break_is_forced(run, raw_body))
{
Some(run) => {
self.break_after_operator_run_doc(run, expr.span().start, raw_body)
}
None => {
let body_doc = prepend_leading(d, terminal_run, raw_body);
if d.will_break(body_doc) {
d.indent_line(body_doc)
} else {
d.concat(&[d.text(" "), ternary_body_parens_group(d, body_doc)])
}
}
}
} else {
let forced = broke_run.as_ref().and_then(|run| {
let body_doc = build_body(&|| self.build_arrow_body_doc(expr));
self.broke_after_run_break_is_forced(run, body_doc)
.then(|| {
self.break_after_operator_run_doc(run, expr.span().start, body_doc)
})
});
match forced {
Some(doc) => doc,
None => d.indent_line(build_body(&|| {
self.build_arrow_body_doc_with_leading(expr, terminal_run)
})),
}
}
}
ast::ArrowFunctionBody::BlockStatement(block) => {
let mut block_parts = DocBuf::new();
self.build_arrow_block_body(&mut block_parts, block, terminal_gap_start);
d.concat(&block_parts)
}
};
let close = match heads_id {
Some(id) if context == ArrowChainContext::Callee => {
d.if_break_with_id(d.softline(), d.empty(), id)
}
_ => d.empty(),
};
let body_part = match heads_id {
Some(id) => d.indent_if_break(body_part, id),
None => body_part,
};
d.group(d.concat(&[heads, d.text(" =>"), body_part, close]))
}
fn arrow_chain_body_stays_on_head_line(
&self,
terminal: &ast::ArrowFunctionBody<'_>,
terminal_gap_start: u32,
should_break_chain: bool,
) -> bool {
let body_start = terminal.span().start;
if self.has_own_line_post_arrow_comment(terminal_gap_start, body_start) {
return false;
}
match terminal {
ast::ArrowFunctionBody::BlockStatement(_) => true,
ast::ArrowFunctionBody::Expression(expr) => {
self.arrow_body_hugs(expr)
|| (!should_break_chain
&& matches!(
expr.kind,
ast::ExpressionKind::ConditionalExpression(_)
)
&& !has_leftmost_arrow_body_parens(expr))
}
}
}
fn build_arrow_return_type_doc(
&self,
annotation: &ast::TSTypeAnnotation<'_>,
params_start: Option<u32>,
) -> DocId {
let close_paren_after = self.return_type_close_paren(params_start, annotation.span.start);
if let Some(frozen) = self.build_frozen_return_type_doc(close_paren_after, annotation) {
return frozen;
}
let comment_prefix = self
.build_close_paren_to_return_type_comments(close_paren_after, annotation.span.start);
let inner_type = unwrap_parenthesized(annotation.type_annotation);
if matches!(inner_type, ast::TSType::Function(_)) {
return self.prepend_opt(
comment_prefix,
self.build_arrow_return_type_annotation_doc(annotation),
);
}
self.prepend_opt(
comment_prefix,
self.build_type_annotation_doc_for_return_type(annotation),
)
}
fn build_arrow_params_doc_ungrouped(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
let build = || {
if self.region_param_is_bare(arrow, span)
&& let [param] = arrow.params
{
self.test_call_flat_params.set(false);
return self.build_function_parameter_doc(param);
}
self.build_params_doc_with_comments(
arrow.params,
arrow.params_start,
self.arrow_params_end(arrow),
)
};
match bare_param_sharing_arrow_start(arrow, span) {
Some(start) => self.with_owned_comment_claimed_above(start, build),
None => build(),
}
}
pub(crate) fn push_async_arrow_head(
&self,
parts: &mut DocBuf,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) {
if !arrow.r#async {
return;
}
let d = self.d();
parts.push(d.text("async"));
let head_start = arrow
.type_parameters
.as_ref()
.map(|tp| tp.span.start)
.or(arrow.params_start)
.or_else(|| arrow.params.first().map(|p| p.span().start))
.unwrap_or_else(|| arrow.body.span().start);
parts.push(self.build_keyword_to_name_comments(span.start, head_start));
}
fn arrow_params_end(&self, arrow: &ast::ArrowFunctionExpression<'_>) -> Option<u32> {
match arrow.params_start {
Some(params_start) => self.close_paren_after_params(
params_start,
arrow.params.last().map(|p| p.span().end),
arrow.body.span().start,
),
None => arrow.params.last().map(|p| p.span().end),
}
}
fn arrow_signature_end(&self, arrow: &ast::ArrowFunctionExpression<'_>, span: Span) -> u32 {
if let Some(rt) = &arrow.return_type {
return rt.span.end;
}
self.arrow_params_end(arrow).unwrap_or_else(|| {
arrow
.params_start
.map_or(span.start, |_| arrow.body.span().start)
})
}
fn arrow_trailing_param_comment_forces_break(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
) -> bool {
self.trailing_param_comment_forces_break(arrow.params, self.arrow_params_end(arrow))
}
fn function_trailing_param_comment_forces_break(
&self,
func: &ast::FunctionExpression<'_>,
) -> bool {
self.trailing_param_comment_forces_break(
func.params,
self.find_closing_paren(func.params_start, func.body.span.start),
)
}
fn trailing_param_comment_forces_break(
&self,
params: &[ast::Expression<'_>],
params_end: Option<u32>,
) -> bool {
let (Some(last_param), Some(params_end)) = (params.last(), params_end) else {
return false;
};
self.comments_to_emit_between(last_param.span().end, params_end)
.any(|comment| self.comment_cannot_glue_to_operator(comment))
}
pub(in crate::printer) fn append_pre_arrow_comments(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
arrow_span: Span,
sig: DocId,
) -> DocId {
let arrow_pos = arrow.arrow_token;
if !self.has_comments_to_emit_between(arrow_span.start, arrow_pos) {
return sig;
}
let sig_end = self.arrow_signature_end(arrow, arrow_span);
if !self.has_comments_to_emit_between(sig_end, arrow_pos) {
return sig;
}
let d = self.d();
let mut parts: DocBuf = smallvec![sig];
for comment in self.comments_to_emit_between(sig_end, arrow_pos) {
parts.push(d.text(" "));
parts.push(self.build_comment_doc(comment));
}
d.concat(&parts)
}
pub(crate) fn build_arrow_signature_doc(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
let d = self.d();
if !arrow.r#async && arrow.type_parameters.is_none() {
return self.build_arrow_signature_tail_doc(arrow, span);
}
let mut parts = DocBuf::new();
self.push_async_arrow_head(&mut parts, arrow, span);
if let Some(tp) = &arrow.type_parameters {
parts.push(self.build_type_parameter_declaration_doc_wrapping(tp));
}
let tail = self.build_arrow_signature_tail_doc(arrow, span);
self.append_signature_head_gap_comments(
&mut parts,
self.type_params_paren_gap(arrow.type_parameters.as_ref()),
None,
tail,
);
d.concat(&parts)
}
fn build_arrow_signature_tail_doc(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
let params_doc = self.build_arrow_params_doc_ungrouped(arrow, span);
match &arrow.return_type {
Some(return_type) => self.d().concat(&[
params_doc,
self.build_arrow_return_type_doc(return_type, arrow.params_start),
]),
None => params_doc,
}
}
fn has_trailing_line_comment_in_params(
&self,
params: &[ast::Expression<'_>],
trailing_comments_end: Option<u32>,
) -> bool {
params.iter().enumerate().any(|(i, param)| {
let param_end = param.printed_end();
let trailing_end = self.param_trailing_end(params, i, trailing_comments_end);
if self.has_line_comments_between(param_end, trailing_end) {
return true;
}
i == params.len() - 1
&& self.has_own_line_block_comment_before_closer(param_end, trailing_end)
})
}
fn param_trailing_end(
&self,
params: &[ast::Expression<'_>],
index: usize,
trailing_comments_end: Option<u32>,
) -> u32 {
params.get(index + 1).map_or_else(
|| trailing_comments_end.unwrap_or_else(|| params[index].span().end),
|next| self.param_start_with_decorators(next),
)
}
fn arrow_body_hugs(&self, expr: &ast::Expression<'_>) -> bool {
arrow_body_always_hugs(expr, self.source)
&& !self.left_spine_shell_has_own_line_comment(expr)
}
pub(in crate::printer) fn build_leftmost_parens_arrow_body_doc(
&self,
expr: &ast::Expression<'_>,
) -> Option<DocId> {
let leftmost_span = leftmost_arrow_body_parens_span(expr)?;
let prev = self
.arrow_body_leftmost_parens_target
.replace(Some(leftmost_span));
let doc = self.build_flat_chain_expression_doc(expr);
self.arrow_body_leftmost_parens_target.set(prev);
Some(doc)
}
pub(in crate::printer) fn build_inner_leftmost_parens_arrow_body_doc(
&self,
expr: &ast::Expression<'_>,
) -> Option<DocId> {
if arrow_body_is_its_own_leftmost_pair(expr) {
return None;
}
self.build_leftmost_parens_arrow_body_doc(expr)
}
fn build_arrow_body_doc(&self, expr: &ast::Expression<'_>) -> DocId {
self.build_arrow_body_doc_with_leading(expr, None)
}
fn build_arrow_body_doc_with_leading(
&self,
expr: &ast::Expression<'_>,
leading: Option<DocId>,
) -> DocId {
let d = self.d();
let prepend = |doc: DocId| prepend_leading(d, leading, doc);
if let Some((span, doc)) = self.arrow_body_inject.get()
&& span == expr.span().start
{
return prepend(doc);
}
self.mark_jsdoc_cast_value_gap(expr);
if let Some(doc) = self.build_leftmost_parens_arrow_body_doc(expr) {
return prepend(doc);
}
if self.arrow_body_takes_ternary_pair(expr) {
let body_doc = self.build_expression_doc_claiming_outermost(expr);
let with_leading = prepend(body_doc);
if d.will_break(body_doc) || leading.is_some_and(|l| d.will_break(l)) {
return with_leading;
}
return d.if_break(with_leading, d.parens(with_leading));
}
if let ast::ExpressionKind::SequenceExpression(seq) = &expr.kind {
return prepend(self.build_sequence_doc(seq, expr.span, SeqLayout::Hanging));
}
if self.needs_parens(expr, ParenContext::ArrowBody) {
prepend(d.parens(self.build_flat_chain_expression_doc(expr)))
} else {
prepend(self.build_value_with_outermost_owned_comment(expr, || {
self.build_flat_chain_expression_doc(expr)
}))
}
}
fn build_arrow_body_with_comments_doc(
&self,
expr: &ast::Expression<'_>,
sig_end: u32,
body_start: u32,
frozen: Option<Span>,
) -> DocId {
let leading = self.arrow_gap_leading_run(sig_end, body_start);
match frozen {
Some(frozen) => {
let position_parens = self.needs_parens(expr, ParenContext::ArrowBody);
let body = self.build_frozen_value_doc(expr, frozen, ParenContext::ArrowBody);
prepend_leading(
self.d(),
leading,
self.wrap_frozen_for_init_in(expr, frozen, position_parens, body),
)
}
None => self.build_arrow_body_doc_with_leading(expr, leading),
}
}
pub(crate) fn has_own_line_post_arrow_comment(&self, sig_end: u32, body_start: u32) -> bool {
self.comment_hangs_value_after_operator(sig_end, body_start)
}
pub(in crate::printer) fn arrow_gap_glued_block_comment(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
body: &ast::Expression<'_>,
) -> Option<u32> {
let gap_start = arrow_token_end(arrow);
let body_start = body.span().start;
let mut comments = self.comments_in_source_between(gap_start, body_start);
let comment = comments.next()?;
(comments.next().is_none()
&& comment.is_block
&& !comment.multiline
&& !self.has_newline_between(gap_start, comment.span.start)
&& self.has_newline_between(comment.span.end, body_start))
.then_some(comment.span.start)
}
pub(crate) fn arrow_gap_broke_after_run(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
sig_end: u32,
body_start: u32,
) -> Option<CommentVec<'a>> {
let ast::ArrowFunctionBody::Expression(body) = &arrow.body else {
return None;
};
if !self.arrow_body_hugs(body) {
return None;
}
let frozen = self.value_head_frozen_span(sig_end, body.span());
if self.arrow_body_shell_holds_comments(body, span.end, frozen) {
return None;
}
self.broke_after_value_leading_run(sig_end, body_start)
}
pub(super) fn build_arrow_doc(
&self,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
self.build_arrow_doc_wrapping(arrow, span)
}
fn build_function_expression_signature_doc(
&self,
func: &ast::FunctionExpression<'_>,
) -> (DocId, u32) {
let d = self.d();
let mut sig_parts = DocBuf::new();
let body_start = func.body.span.start;
if let Some(type_params) = &func.type_parameters {
sig_parts.push(self.build_type_parameter_declaration_doc_wrapping(type_params));
}
let (sig_doc, sig_end) = self.build_callable_signature_doc(
func.params,
func.type_parameters.as_ref(),
func.return_type.as_ref(),
func.params_start,
body_start,
);
self.append_signature_head_gap_comments(
&mut sig_parts,
self.type_params_paren_gap(func.type_parameters.as_ref()),
None,
sig_doc,
);
(d.concat(&sig_parts), sig_end)
}
pub(in crate::printer) fn build_function_doc_body(
&self,
func: &ast::FunctionExpression<'_>,
) -> DocId {
let d = self.d();
let (sig_doc, sig_end) = self.build_function_expression_signature_doc(func);
let mut parts: DocBuf = smallvec![sig_doc];
self.append_body_with_sig_comments(&mut parts, sig_end, &func.body);
d.concat(&parts)
}
pub(in crate::printer) fn build_function_doc(
&self,
func: &ast::FunctionExpression<'_>,
) -> DocId {
let d = self.d();
let mut parts: DocBuf = DocBuf::new();
let head_end = self.push_function_keyword_head(
&mut parts,
func.span.start,
func.id
.as_ref()
.map_or(func.params_start, |id| id.span.start),
FunctionHeadModifier::from_async(func.r#async),
func.generator,
);
if let Some(id) = &func.id {
parts.push(self.build_keyword_to_name_comments(head_end, id.span.start));
parts.push(self.build_identifier_doc(id));
self.push_signature_head_comments(
&mut parts,
id.span.end,
func.type_parameters.as_ref(),
func.params_start,
);
}
if func.id.is_none() {
let next_start = func
.type_parameters
.as_ref()
.map_or(func.params_start, |tp| tp.span.start);
parts.push(self.build_keyword_to_name_comments(head_end, next_start));
}
parts.push(self.build_function_doc_body(func));
d.concat(&parts)
}
fn build_function_parameter_item_doc(
&self,
list_frozen: Option<Span>,
param: &ast::Expression<'_>,
) -> DocId {
if let Some(frozen) = list_frozen {
return self.build_frozen_span_doc(frozen);
}
self.build_list_element_doc(param.span().start, || {
self.build_frozen_param_binding_doc(param)
.unwrap_or_else(|| self.build_function_parameter_doc(param))
})
}
pub(crate) fn build_params_doc_with_comments(
&self,
params: &[ast::Expression<'_>],
params_start: Option<u32>,
trailing_comments_end: Option<u32>,
) -> DocId {
let d = self.d();
let test_call_flat = self.test_call_flat_params.replace(false);
if params.is_empty() {
return self
.build_empty_params_with_comments_doc(params_start, self.source.len() as u32);
}
let comments_present = {
let window_start = params_start.unwrap_or_else(|| params[0].span().start);
let last_end = params[params.len() - 1].span().end;
let window_end = trailing_comments_end.map_or(last_end, |end| end.max(last_end));
self.has_comments_on_page_between(window_start, window_end)
};
let should_hug_single_pattern = self.hugs_sole_parameter(
params,
params_start,
trailing_comments_end,
comments_present,
);
if should_hug_single_pattern {
let param_doc = self
.build_group_less_hugged_param_doc(¶ms[0], comments_present)
.unwrap_or_else(|| self.build_function_parameter_doc(¶ms[0]));
return d.parens(param_doc);
}
let (paren_prefix, paren_pull_pos) = match params_start {
Some(open) if comments_present => self
.delimiter_line_comment_prefix(open, self.param_start_with_decorators(¶ms[0])),
_ => (None, None),
};
let has_trailing_line_comment = comments_present
&& self.has_trailing_line_comment_in_params(params, trailing_comments_end);
let has_leading_own_line_comment =
comments_present && self.has_leading_own_line_comment_in_params(params, params_start);
let should_break_for_param_properties = params.len() > 1
&& params
.iter()
.any(|p| matches!(p.kind, ast::ExpressionKind::TSParameterProperty(_)));
let has_blank_line_between_params = self.has_blank_line_between_params(params);
let force_break = has_trailing_line_comment
|| has_leading_own_line_comment
|| should_break_for_param_properties
|| (has_blank_line_between_params && !test_call_flat)
|| paren_pull_pos.is_some();
let flat_list = test_call_flat && !force_break;
let mut inner_parts = d.pooled_docbuf();
let mut prev_comma_pos: Option<u32> = None;
let mut prev_param_end: Option<u32> = None;
for (i, param) in params.iter().enumerate() {
let param_start = param.span().start;
let is_last = i == params.len() - 1;
let gap_start = prev_param_end.unwrap_or_else(|| {
params_start.map_or(param_start, |pos| pos + 1)
});
let skip_delim = if i == 0 { paren_pull_pos } else { None };
let param_render_start = self.param_start_with_decorators(param);
if i > 0 {
if force_break {
let blank_start = self.element_shell_end(gap_start, param_start);
let content_start = if comments_present {
self.param_content_start(
gap_start,
param_render_start,
prev_comma_pos,
skip_delim,
param,
)
} else {
param_start
};
self.push_next_line_empty_hardline(
&mut inner_parts,
blank_start,
content_start,
);
} else if flat_list {
inner_parts.push(d.text(" "));
} else {
inner_parts.push(d.line());
}
}
if comments_present {
inner_parts.push(self.build_leading_param_comments(
gap_start,
param_render_start,
prev_comma_pos,
skip_delim,
));
}
let list_frozen = self.param_frozen_span(params_start, params, i);
inner_parts.push(self.build_function_parameter_item_doc(list_frozen, param));
let param_end = Self::element_claim_anchor(
list_frozen.or_else(|| self.param_binding_frozen_span(param)),
param.printed_end(),
);
let search_end = self.param_trailing_end(params, i, trailing_comments_end);
let comma_pos = comments_present
.then(|| self.find_comma_in_range(param_end, search_end))
.flatten();
if is_last {
if comments_present {
inner_parts.extend(self.build_trailing_gap_comments(param_end, search_end));
}
} else {
let anchor_line_end = if comments_present {
self.gap_anchor_line_end(param_end, search_end)
} else {
param_end
};
let same_line_blocks: CommentVec<'_> = if comments_present {
self.comments_to_emit_between(param_end, search_end)
.filter(|c| c.is_block && c.span.start < anchor_line_end)
.collect()
} else {
CommentVec::new()
};
for comment in same_line_blocks
.iter()
.filter(|c| comma_pos.is_none_or(|pos| c.span.start < pos))
{
inner_parts.push(d.text(" "));
inner_parts.push(self.build_comment_doc(comment));
}
inner_parts.push(d.text(","));
if let Some(cp) = comma_pos {
let next_start = self.param_start_with_decorators(¶ms[i + 1]);
self.push_stranded_after_comma_blocks(&mut inner_parts, cp, next_start);
}
if comments_present {
for comment in
self.comments_to_emit_between(param_end, search_end)
.filter(|c| {
self.param_trailing_line_comment(
c,
param_end,
anchor_line_end,
comma_pos,
)
})
{
inner_parts.push(self.build_trailing_line_comment_doc(comment));
}
}
}
prev_comma_pos = comma_pos;
prev_param_end = Some(param_end);
}
let mut result: DocBuf = smallvec![d.text("(")];
if let Some(prefix) = paren_prefix {
result.push(prefix);
}
if force_break {
result.push(d.indent_hardline(d.concat(&inner_parts)));
result.push(d.hardline());
} else if flat_list {
result.push(d.concat(&inner_parts));
} else {
result.push(d.indent_softline(d.concat(&inner_parts)));
result.push(d.softline());
}
result.push(d.text(")"));
d.concat(&result)
}
pub(in crate::printer) fn has_leading_own_line_comment_in_params(
&self,
params: &[ast::Expression<'_>],
params_start: Option<u32>,
) -> bool {
for (i, param) in params.iter().enumerate() {
let search_start = if i == 0 {
params_start.map_or_else(|| param.span().start, |pos| pos + 1)
} else {
params[i - 1].printed_end()
};
if self
.has_own_line_comment_between(search_start, self.param_start_with_decorators(param))
{
return true;
}
}
false
}
fn has_own_line_comment_between(&self, start: u32, end: u32) -> bool {
self.comments_on_page_between(start, end).any(|c| {
!c.is_block || self.block_comment_owns_its_line(c, true)
})
}
pub(in crate::printer) fn has_blank_line_between_params(
&self,
params: &[ast::Expression<'_>],
) -> bool {
params.windows(2).any(|pair| {
self.is_next_line_empty(
pair[0].span().end,
self.param_start_with_decorators(&pair[1]),
)
})
}
fn leading_param_comments(
&self,
start: u32,
param_render_start: u32,
prev_comma_pos: Option<u32>,
skip_delim: Option<u32>,
) -> CommentVec<'_> {
let anchor_line_end = self.gap_anchor_line_end(start, param_render_start);
self.comments_to_emit_between(start, param_render_start)
.filter(|c| {
if let Some(dpos) = skip_delim
&& self.comment_on_delimiter_line(dpos, c)
{
return false;
}
let Some(comma) = prev_comma_pos else {
return true; };
if c.span.start >= comma
&& self.is_stranded_after_comma_block(c, comma, param_render_start)
{
return false;
}
if self.param_trailing_line_comment(c, start, anchor_line_end, Some(comma)) {
return false;
}
if c.span.start >= anchor_line_end {
return true;
}
c.is_block && c.span.start >= comma
})
.collect()
}
fn param_trailing_line_comment(
&self,
comment: &ast::Comment,
param_end: u32,
anchor_line_end: u32,
comma_pos: Option<u32>,
) -> bool {
if comment.is_block {
return false;
}
let anchored = comment.span.start < anchor_line_end
|| comma_pos.is_some_and(|comma| {
comment.span.start >= comma && self.comment_on_comma_line(comma, comment)
});
anchored && !self.gap_emitted_line_comment_before(param_end, comment.span.start)
}
fn param_content_start(
&self,
start: u32,
param_render_start: u32,
prev_comma_pos: Option<u32>,
skip_delim: Option<u32>,
param: &ast::Expression<'_>,
) -> u32 {
self.leading_param_comments(start, param_render_start, prev_comma_pos, skip_delim)
.first()
.map(|c| c.span.start)
.or_else(|| {
self.owned_leading_comment_start(param)
.filter(|&p| p >= start && p < param_render_start)
})
.unwrap_or(param_render_start)
}
fn build_leading_param_comments(
&self,
start: u32,
param_render_start: u32,
prev_comma_pos: Option<u32>,
skip_delim: Option<u32>,
) -> DocId {
let d = self.d();
let comments =
self.leading_param_comments(start, param_render_start, prev_comma_pos, skip_delim);
if comments.is_empty() {
return d.empty();
}
let mut parts: DocBuf = DocBuf::new();
self.push_leading_comment_run(
&mut parts,
comments.iter().copied(),
param_render_start,
LeadingGlue::Adjacent,
);
d.concat(&parts)
}
pub(in crate::printer) fn build_class_expression_doc(
&self,
class_expr: &ast::ClassExpression<'_>,
class_expr_span: Span,
) -> DocId {
let d = self.d();
let first_keyword_start = self.find_keyword_after_decorators(
class_expr.decorators,
if class_expr.r#abstract {
"abstract"
} else {
"class"
},
class_expr_span.start,
);
let abstract_end = class_expr
.r#abstract
.then(|| first_keyword_start + "abstract".len() as u32);
let class_keyword_start = abstract_end
.and_then(|end| self.find_keyword_in_range(end, class_expr_span.end, "class"))
.unwrap_or(first_keyword_start);
let positions = self.class_heritage_positions(
class_keyword_start,
class_expr.id.as_ref(),
class_expr.type_parameters.as_ref(),
class_expr.super_class,
class_expr.super_type_parameters.as_ref(),
class_expr.implements,
);
let layout = self.class_header_layout(
&positions,
class_expr.super_class,
class_expr.super_type_parameters.as_ref(),
class_expr.implements,
);
let mut parts = DocBuf::new();
if let Some(dec_doc) = self.build_decorators_doc(class_expr.decorators, first_keyword_start)
{
parts.push(dec_doc);
}
if let Some(end) = abstract_end {
parts.push(d.text("abstract"));
if let Some(c) = self.build_inline_comments_between_doc_opt(end, class_keyword_start) {
parts.push(c);
}
parts.push(d.text(" "));
}
parts.push(d.text("class"));
if let Some(id) = &class_expr.id {
parts.push(self.build_keyword_to_name_comments(class_keyword_start, id.span.start));
parts.push(self.build_identifier_doc(id));
}
self.push_class_type_params(
&mut parts,
class_expr.type_parameters.as_ref(),
class_expr
.id
.as_ref()
.map_or(ClassTypeParamsGap::Keyword(class_keyword_start), |id| {
ClassTypeParamsGap::Name(id.span.end)
}),
);
let extends_doc = self.build_class_extends_doc(
class_expr.super_class,
class_expr.super_type_parameters.as_ref(),
positions.extends_keyword_start,
);
let implements_doc = self.build_class_implements_doc(
class_expr.implements,
layout.is_group(),
positions.implements_keyword_start,
);
let frozen_body = self.gap_frozen_span(positions.header_end, class_expr.body.span);
let header_doc = self.build_class_header_doc(
parts,
&positions,
extends_doc,
implements_doc,
class_expr.implements,
ClassHeaderOptions {
body_is_empty: class_expr.body.body.is_empty(),
body_start: class_expr.body.span.start,
layout,
body_frozen: frozen_body.is_some(),
},
);
d.concat(&[
header_doc,
self.build_class_body_doc(&class_expr.body, frozen_body),
])
}
}