use super::super::{
ArrowChainContext, AssumedBreak, CommentSpacing, Printer, ShareTag, arrow_chain_should_break,
is_curried_arrow_chain, is_multiline_template_expression,
};
use super::arg_comments::{
any_arg_gap_has_comment_on_page, build_arg_gap_docs, build_empty_args_parens_doc,
emit_first_arg_leading_comments, emit_last_arg_trailing_comments,
};
use super::arg_predicates::{
arrow_body_has_leading_own_line_comment, arrow_body_is_call_through_non_null,
is_block_function, is_react_hook_call_with_deps_array, is_short_second_arg_for_expand_first,
is_ternary_arrow_body,
};
use crate::ast;
use crate::printer::expressions::functions::{
arrow_signature_has_breaking_comments, arrow_signature_refuses_hug, arrow_token_end,
flat_sole_param_literal, function_return_hug_break, prepend_leading,
};
use smallvec::{SmallVec, smallvec};
use tsv_lang::Span;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::{DocArena, DocId};
use tsv_lang::source_scan::has_newline_before_position;
pub(super) fn build_printed_argument_doc(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
build: impl FnOnce() -> DocId,
) -> DocId {
let outer_skip = printer.skip_arrow_chain.replace(false);
let doc = if is_curried_arrow_chain(arg) {
printer.build_with_arrow_chain_context(ArrowChainContext::CallArgOrBinaryish, build)
} else {
build()
};
printer.skip_arrow_chain.set(outer_skip);
doc
}
pub(super) fn build_arrow_sig_doc(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> DocId {
let d = printer.d();
let sig = printer.append_pre_arrow_comments(
arrow,
span,
printer.build_arrow_signature_doc(arrow, span),
);
let sig = if arrow_has_return_or_type_params(arrow)
|| printer.arrow_empty_params_hold_comments(arrow)
{
d.group(sig)
} else {
d.remove_lines(sig)
};
printer.prepend_owned_leading_comment_at(span.start, sig)
}
pub(super) fn build_hugged_arrow_sig_doc(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
) -> Option<DocId> {
(!arrow_signature_refuses_hug(printer, arrow, span))
.then(|| build_arrow_sig_doc(printer, arrow, span))
}
pub(super) fn prepend_arrow_body_comments(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
body_start: u32,
body_doc: DocId,
) -> DocId {
let arrow_end = arrow_token_end(arrow);
if let Some(run) = printer.value_gap_soft_broke_after_run(arrow_end, body_start)
&& !printer.broke_after_run_break_is_forced(&run, body_doc)
{
let d = printer.d();
let mut parts = DocBuf::new();
printer.push_leading_run_with_soft_line(&mut parts, &run);
return d.concat(&[d.group_break(d.concat(&parts)), body_doc]);
}
if let Some(run) = printer.broke_after_value_leading_run(arrow_end, body_start) {
return printer.leading_run_before_breaking_value_doc(&run, body_start, body_doc);
}
prepend_hugged_arrow_body_comments(printer, arrow, body_start, body_doc)
}
pub(super) fn prepend_hugged_arrow_body_comments(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
body_start: u32,
body_doc: DocId,
) -> DocId {
let arrow_end = arrow_token_end(arrow);
if let Some(lc) = printer.build_rhs_comments_glued_opt(arrow_end, body_start) {
printer.d().concat(&[lc, body_doc])
} else {
body_doc
}
}
#[inline]
pub(super) fn wrap_call_with_soft_breaks(d: &DocArena, callee: DocId, args: DocId) -> DocId {
ArgOpener::callee(d, callee).wrap_soft(d, args)
}
#[inline]
pub(super) fn wrap_call_with_hard_breaks_paren_line(
d: &DocArena,
callee: DocId,
paren_line: &[DocId],
args: DocId,
) -> DocId {
ArgOpener::callee(d, callee).wrap_hard_broken(d, paren_line, args)
}
#[inline]
pub(super) fn wrap_call_with_will_break_guard(d: &DocArena, callee: DocId, args: DocId) -> DocId {
if d.will_break(args) {
d.concat(&[
callee,
d.group_break(d.concat(&[
d.text("("),
d.indent_softline(args),
d.softline(),
d.text(")"),
])),
])
} else {
wrap_call_with_soft_breaks(d, callee, args)
}
}
pub(super) fn arg_needs_soft_wrap(arg: &ast::Expression<'_>) -> bool {
matches!(
arg.kind,
ast::ExpressionKind::CallExpression(_)
| ast::ExpressionKind::MemberExpression(_)
| ast::ExpressionKind::NewExpression(_)
| ast::ExpressionKind::Identifier(_)
| ast::ExpressionKind::ThisExpression(_)
| ast::ExpressionKind::ConditionalExpression(_)
)
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub(super) enum ChainArgKind {
HugsNaturally,
NeedsWrapper,
NeedsSoftWrap,
}
pub(super) fn classify_chain_arg(arg: &ast::Expression<'_>) -> ChainArgKind {
match &arg.kind {
ast::ExpressionKind::ObjectExpression(_)
| ast::ExpressionKind::ArrayExpression(_)
| ast::ExpressionKind::FunctionExpression(_) => ChainArgKind::HugsNaturally,
ast::ExpressionKind::TSAsExpression(e) => classify_chain_arg(e.expression),
ast::ExpressionKind::TSSatisfiesExpression(e) => classify_chain_arg(e.expression),
ast::ExpressionKind::TSTypeAssertion(e) => classify_chain_arg(e.expression),
ast::ExpressionKind::ArrowFunctionExpression(arrow) => classify_arrow_body(arrow),
_ => ChainArgKind::NeedsSoftWrap,
}
}
fn arrow_has_return_or_type_params(arrow: &ast::ArrowFunctionExpression<'_>) -> bool {
arrow.return_type.is_some() || arrow.type_parameters.is_some()
}
fn classify_arrow_body(arrow: &ast::ArrowFunctionExpression<'_>) -> ChainArgKind {
match &arrow.body {
ast::ArrowFunctionBody::BlockStatement(_) => ChainArgKind::HugsNaturally,
ast::ArrowFunctionBody::Expression(expr) => classify_expression_body(expr),
}
}
pub(super) fn could_expand_arrow_chain(arrow: &ast::ArrowFunctionExpression<'_>) -> bool {
arrow_terminal_expression_body(arrow).is_none_or(|body| {
matches!(
body.kind,
ast::ExpressionKind::ObjectExpression(_) | ast::ExpressionKind::ArrayExpression(_)
)
})
}
fn could_expand_arrow_arg(arrow: &ast::ArrowFunctionExpression<'_>) -> bool {
could_expand_arrow_chain(arrow)
|| match &arrow.body {
ast::ArrowFunctionBody::Expression(body) => {
arrow_body_is_call_through_non_null(body) || is_ternary_arrow_body(body)
}
ast::ArrowFunctionBody::BlockStatement(_) => false,
}
}
fn classify_expression_body(expr: &ast::Expression<'_>) -> ChainArgKind {
match &expr.kind {
ast::ExpressionKind::ObjectExpression(_) | ast::ExpressionKind::ArrayExpression(_) => {
ChainArgKind::HugsNaturally
}
ast::ExpressionKind::ConditionalExpression(_) => ChainArgKind::NeedsWrapper,
ast::ExpressionKind::ArrowFunctionExpression(inner) => classify_arrow_body(inner),
_ => ChainArgKind::NeedsSoftWrap,
}
}
fn build_arrow_body_like_arrow(printer: &Printer<'_>, body_expr: &ast::Expression<'_>) -> DocId {
let prev = printer.arrow_chain_context.replace(ArrowChainContext::None);
let doc = printer.build_arrow_arg_body_doc(body_expr);
printer.arrow_chain_context.set(prev);
doc
}
impl Printer<'_> {
pub(super) fn build_arrow_arg_body_doc(&self, body_expr: &ast::Expression<'_>) -> DocId {
self.build_inner_leftmost_parens_arrow_body_doc(body_expr)
.unwrap_or_else(|| self.build_expression_doc_claiming_outermost(body_expr))
}
pub(super) fn build_arrow_arg_body_doc_unclaimed(
&self,
body_expr: &ast::Expression<'_>,
) -> DocId {
self.build_inner_leftmost_parens_arrow_body_doc(body_expr)
.unwrap_or_else(|| self.build_expression_doc(body_expr))
}
}
pub(super) fn prebuild_expand_last_break_body(
printer: &Printer<'_>,
last_arg: Option<&ast::Expression<'_>>,
) -> Option<(u32, DocId)> {
if let Some(ast::Expression {
kind: ast::ExpressionKind::ArrowFunctionExpression(arrow),
..
}) = last_arg
&& let ast::ArrowFunctionBody::Expression(body_expr) = &arrow.body
&& (arrow_body_is_call_through_non_null(body_expr)
|| matches!(
body_expr.kind,
ast::ExpressionKind::ConditionalExpression(_)
))
{
let share_key = printer.chain_share_key(body_expr, ShareTag::ExpandLastBreakBody);
let body_doc = printer.chain_shared_doc(share_key, || {
build_arrow_body_like_arrow(printer, body_expr)
});
return Some((body_expr.span().start, body_doc));
}
None
}
fn arrow_hug_body_needs_parens(body_expr: &ast::Expression<'_>) -> bool {
matches!(body_expr.kind, ast::ExpressionKind::ObjectExpression(_))
}
fn terminal_arrow<'a, 'arena>(
arrow: &'a ast::ArrowFunctionExpression<'arena>,
) -> (&'a ast::ArrowFunctionExpression<'arena>, Option<Span>) {
let mut current = arrow;
let mut span = None;
loop {
let ast::ArrowFunctionBody::Expression(body) = ¤t.body else {
return (current, span);
};
let ast::ExpressionKind::ArrowFunctionExpression(inner) = &body.kind else {
return (current, span);
};
current = inner;
span = Some(body.span);
}
}
fn arrow_terminal_expression_body<'arena>(
arrow: &ast::ArrowFunctionExpression<'arena>,
) -> Option<&'arena ast::Expression<'arena>> {
match &terminal_arrow(arrow).0.body {
ast::ArrowFunctionBody::Expression(body) => Some(body),
ast::ArrowFunctionBody::BlockStatement(_) => None,
}
}
pub(super) fn arrow_body_expands_internally(arrow: &ast::ArrowFunctionExpression<'_>) -> bool {
arrow_terminal_expression_body(arrow).is_some_and(|body| {
matches!(
body.kind,
ast::ExpressionKind::ObjectExpression(_) | ast::ExpressionKind::ArrayExpression(_)
)
})
}
pub(super) fn prebuild_expand_last_obj_array_body(
printer: &Printer<'_>,
last_arg: Option<&ast::Expression<'_>>,
) -> Option<(u32, DocId)> {
let d = printer.d();
let ast::ExpressionKind::ArrowFunctionExpression(arrow) = &(last_arg?).kind else {
return None;
};
let body_expr = arrow_terminal_expression_body(arrow)?;
if !matches!(
body_expr.kind,
ast::ExpressionKind::ObjectExpression(_) | ast::ExpressionKind::ArrayExpression(_)
) {
return None;
}
let share_key = printer.chain_share_key(body_expr, ShareTag::ExpandLastBody);
let doc = printer.chain_shared_doc(share_key, || {
let raw = build_arrow_body_like_arrow(printer, body_expr);
if arrow_hug_body_needs_parens(body_expr) {
d.parens(raw)
} else {
raw
}
});
Some((body_expr.span().start, doc))
}
pub(super) fn build_args_split_last(
arguments: &[&ast::Expression<'_>],
printer: &Printer<'_>,
paren_open: u32,
has_comments: bool,
) -> (DocBuf, DocId, DocId) {
let d = printer.d();
let arg_docs: DocBuf = arguments
.iter()
.enumerate()
.map(|(i, _)| build_joined_argument_doc(printer, paren_open, arguments, i))
.collect();
let leading_comment_doc = if has_comments {
printer.build_rhs_comments_glued_opt(paren_open, arguments[0].span().start)
} else {
None
};
let mut head_parts = DocBuf::new();
if let Some(lc) = leading_comment_doc {
head_parts.push(lc);
}
let gap_blocks: SmallVec<[(DocBuf, DocBuf); 4]> = if has_comments {
arguments
.windows(2)
.map(|pair| build_arg_gap_docs(printer, pair[0].span().end, pair[1].span().start))
.collect()
} else {
SmallVec::new()
};
for (i, doc) in arg_docs.iter().take(arg_docs.len() - 1).enumerate() {
head_parts.push(*doc);
if let Some((through_comma, leading)) = gap_blocks.get(i) {
head_parts.extend(through_comma.iter().copied());
head_parts.push(d.text(" "));
head_parts.extend(leading.iter().copied());
} else {
head_parts.push(d.text(", "));
}
}
let last_arg_doc = arg_docs[arg_docs.len() - 1];
let mut all_args_parts = DocBuf::new();
if let Some(lc) = leading_comment_doc {
all_args_parts.push(lc);
}
for (i, doc) in arg_docs.iter().enumerate() {
all_args_parts.push(*doc);
if i + 1 < arg_docs.len() {
if let Some((through_comma, leading)) = gap_blocks.get(i) {
all_args_parts.extend(through_comma.iter().copied());
all_args_parts.push(d.line());
all_args_parts.extend(leading.iter().copied());
} else {
all_args_parts.push(d.comma_line());
}
}
}
let all_args_broken = d.concat(&all_args_parts);
(head_parts, last_arg_doc, all_args_broken)
}
pub(super) fn try_hook_deps_args_doc(
printer: &Printer<'_>,
args: &[&ast::Expression<'_>],
paren_open: u32,
call_end: u32,
has_comments: bool,
opener: DocId,
) -> Option<DocId> {
is_react_hook_call_with_deps_array(args, || {
has_comments && any_arg_gap_has_comment_on_page(args, printer, paren_open, call_end)
})
.then(|| build_hook_deps_args_doc(printer, args, paren_open, opener))
}
fn build_hook_deps_args_doc(
printer: &Printer<'_>,
args: &[&ast::Expression<'_>],
paren_open: u32,
prefix: DocId,
) -> DocId {
let d = printer.d();
let mut parts = DocBuf::new();
parts.push(prefix);
for i in 0..args.len() {
if i > 0 {
parts.push(d.text(", "));
}
parts.push(build_joined_argument_doc(printer, paren_open, args, i));
}
parts.push(d.text(")"));
d.concat(&parts)
}
pub(super) struct ArrowGapBreak {
pub(crate) inject: Option<(u32, DocId)>,
}
pub(super) fn last_arg_arrow_gap_break(
printer: &Printer<'_>,
last_arg: &ast::Expression<'_>,
) -> Option<ArrowGapBreak> {
let ast::ExpressionKind::ArrowFunctionExpression(outer) = &last_arg.kind else {
return None;
};
if !could_expand_arrow_arg(outer) {
return None;
}
let (arrow, terminal_span) = terminal_arrow(outer);
let arrow_span = terminal_span.unwrap_or(last_arg.span);
let sig_end = arrow_token_end(arrow);
let body_start = arrow.body.span().start;
let refused = || arrow_signature_refuses_hug(printer, outer, last_arg.span);
if printer.has_own_line_post_arrow_comment(sig_end, body_start) {
return (!refused()).then(|| ArrowGapBreak {
inject: prebuild_arrow_gap_break_body(printer, Some(last_arg)),
});
}
printer.arrow_gap_broke_after_run(arrow, arrow_span, sig_end, body_start)?;
let inject = prebuild_arrow_gap_break_body(printer, Some(last_arg))?;
(printer.d().will_break(inject.1) && !refused()).then_some(ArrowGapBreak {
inject: Some(inject),
})
}
pub(super) fn arrow_body_tail_has_comments(
printer: &Printer<'_>,
span: Span,
body_expr: &ast::Expression<'_>,
) -> bool {
printer.has_comments_on_page_between(body_expr.span().end, span.end)
}
pub(super) fn arrow_hug_refused_by_comments(
printer: &Printer<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
span: Span,
body_expr: &ast::Expression<'_>,
) -> bool {
arrow_signature_has_breaking_comments(printer, arrow, span)
|| arrow_body_tail_has_comments(printer, span, body_expr)
}
fn build_arrow_gap_break_state(
d: &DocArena,
opener: ArgOpener,
head_parts: &[DocId],
last_arg_doc: DocId,
) -> DocId {
d.group_break(d.concat(&[
opener.open,
d.concat(head_parts),
d.group_break(last_arg_doc),
d.softline(),
d.text(")"),
]))
}
fn build_arrow_gap_break_ladder(
d: &DocArena,
opener: ArgOpener,
head_parts: &[DocId],
expanded_last_arg_doc: DocId,
all_args_broken: DocId,
) -> DocId {
opener.ladder(
d,
&[
build_arrow_gap_break_state(d, opener, head_parts, expanded_last_arg_doc),
opener.expand_all_state(d, all_args_broken),
],
)
}
fn build_arrow_gap_break_arg_docs(
printer: &Printer<'_>,
inject: Option<(u32, DocId)>,
arg: &ast::Expression<'_>,
build: impl Fn() -> DocId,
) -> HugArgDocs {
let Some(inject) = inject else {
let one = build_printed_argument_doc(printer, arg, &build);
return HugArgDocs {
expanded: one,
printed: one,
};
};
printer.with_arrow_body_inject(Some(inject), || HugArgDocs {
printed: build_printed_argument_doc(printer, arg, &build),
expanded: build_expand_last_arg_doc(printer, &build),
})
}
fn build_arrow_gap_break_expanded(
printer: &Printer<'_>,
inject: Option<(u32, DocId)>,
printed: DocId,
build: impl FnOnce() -> DocId,
) -> DocId {
if inject.is_none() {
return printed;
}
printer.with_arrow_body_inject(inject, || build_expand_last_arg_doc(printer, build))
}
pub(super) fn build_arrow_gap_break_single_arg_doc(
printer: &Printer<'_>,
opener: ArgOpener,
arg: &ast::Expression<'_>,
gap_break: &ArrowGapBreak,
leading: Option<DocId>,
build: impl Fn() -> DocId,
) -> DocId {
let d = printer.d();
let docs = build_arrow_gap_break_arg_docs(printer, gap_break.inject, arg, build)
.with_leading(d, leading);
build_arrow_gap_break_ladder(d, opener, &[], docs.expanded, docs.printed)
}
pub(super) fn build_arrow_gap_break_multi_arg_doc(
printer: &Printer<'_>,
opener: ArgOpener,
inject: Option<(u32, DocId)>,
head_parts: &[DocId],
printed_last_arg_doc: DocId,
all_args_broken: DocId,
build: impl FnOnce() -> DocId,
) -> DocId {
let d = printer.d();
if let Some(bail) = opener.expand_all_if_head_breaks(d, head_parts, all_args_broken) {
return bail;
}
let expanded = build_arrow_gap_break_expanded(printer, inject, printed_last_arg_doc, build);
build_arrow_gap_break_ladder(d, opener, head_parts, expanded, all_args_broken)
}
fn prebuild_arrow_gap_break_body(
printer: &Printer<'_>,
last_arg: Option<&ast::Expression<'_>>,
) -> Option<(u32, DocId)> {
if let Some(obj) = prebuild_expand_last_obj_array_body(printer, last_arg) {
return Some(obj);
}
let ast::ExpressionKind::ArrowFunctionExpression(arrow) = &(last_arg?).kind else {
return None;
};
match &terminal_arrow(arrow).0.body {
ast::ArrowFunctionBody::BlockStatement(block) => {
Some((block.span.start, printer.arrow_block_body_doc(block)))
}
ast::ArrowFunctionBody::Expression(body) => Some((
body.span().start,
build_arrow_body_like_arrow(printer, body),
)),
}
}
pub(super) fn build_single_container_arg_doc(
printer: &Printer<'_>,
callee: DocId,
arg: &ast::Expression<'_>,
) -> DocId {
let d = printer.d();
let arg_doc = printer.build_expression_doc(arg);
ArgOpener::callee(d, callee).lone_hug_ladder(d, arg_doc, false)
}
pub(super) fn build_expand_last_arg_doc(
printer: &Printer<'_>,
build: impl FnOnce() -> DocId,
) -> DocId {
let prev_skip = printer.skip_arrow_chain.replace(true);
let prev_flat = printer.expand_last_arg_flat_params.replace(true);
let doc = build();
printer.skip_arrow_chain.set(prev_skip);
printer.expand_last_arg_flat_params.set(prev_flat);
doc
}
pub(super) fn build_flat_params_arg_doc(
printer: &Printer<'_>,
build: impl FnOnce() -> DocId,
) -> DocId {
let prev_flat = printer.expand_last_arg_flat_params.replace(true);
let doc = build();
printer.expand_last_arg_flat_params.set(prev_flat);
doc
}
#[derive(Clone, Copy)]
pub(super) struct HugArgDocs {
pub(crate) expanded: DocId,
pub(crate) printed: DocId,
}
impl HugArgDocs {
pub(super) fn with_leading(self, d: &DocArena, leading: Option<DocId>) -> Self {
let Some(leading) = leading else {
return self;
};
let printed = prepend_leading(d, Some(leading), self.printed);
let expanded = if self.expanded == self.printed {
printed
} else {
prepend_leading(d, Some(leading), self.expanded)
};
Self { expanded, printed }
}
}
fn build_arrow_hug_arg_docs(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
build: impl Fn() -> DocId,
) -> HugArgDocs {
let inject = prebuild_expand_last_obj_array_body(printer, Some(arg));
if inject.is_none() {
let one = build_arrow_hug_printed_doc(printer, arg, arrow, build);
return HugArgDocs {
expanded: one,
printed: one,
};
}
printer.with_arrow_body_inject(inject, || {
let printed = build_printed_argument_doc(printer, arg, &build);
let expanded = build_expand_last_arg_doc(printer, &build);
HugArgDocs { expanded, printed }
})
}
pub(super) fn build_arrow_hug_printed_doc(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
build: impl FnOnce() -> DocId,
) -> DocId {
if !arrow_body_expands_internally(arrow)
&& is_curried_arrow_chain(arg)
&& arrow_chain_should_break(arrow)
{
let prev = printer.skip_arrow_chain.replace(true);
let doc = build();
printer.skip_arrow_chain.set(prev);
return doc;
}
build_printed_argument_doc(printer, arg, build)
}
pub(super) fn build_single_arrow_hug_doc(
printer: &Printer<'_>,
opener: ArgOpener,
arg: &ast::Expression<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
leading: Option<DocId>,
build: impl Fn() -> DocId,
) -> DocId {
let d = printer.d();
let expands_internally = arrow_body_expands_internally(arrow);
let assumed = if expands_internally {
None
} else {
AssumedParamHug::new(printer, arg)
};
let docs = AssumedParamHug::sharing(assumed.as_ref(), printer, || {
build_arrow_hug_arg_docs(printer, arg, arrow, &build)
})
.with_leading(d, leading);
let assumed = assumed.and_then(|hug| {
hug.build(printer, false, || {
build_arrow_hug_printed_doc(printer, arg, arrow, &build)
})
.literal
.map(|assumed| assumed.map(|doc| prepend_leading(d, leading, doc)))
});
build_single_arrow_arg_states(d, opener, docs, expands_internally, assumed)
}
pub(in crate::printer) struct AssumedParamHug<'a> {
arg: &'a ast::Expression<'a>,
literal: Option<u32>,
return_break: Option<AssumedBreak>,
return_hug: Option<AssumedBreak>,
body: (u32, DocId),
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::printer) enum AssumedHugKind {
ParamLiteral,
ReturnType,
}
#[derive(Clone, Copy, Default)]
pub(super) struct AssumedHugStates {
pub(super) literal: Option<AssumedHugDoc>,
pub(super) return_hug: Option<DocId>,
}
#[derive(Clone, Copy)]
pub(super) struct AssumedHugDoc {
pub(super) doc: DocId,
probe: Option<DocId>,
}
impl AssumedHugDoc {
#[inline]
pub(super) fn map(self, f: impl FnOnce(DocId) -> DocId) -> Self {
Self {
doc: f(self.doc),
probe: self.probe,
}
}
#[inline]
pub(super) fn state(self, d: &DocArena, line: DocId, contents: DocId) -> DocId {
d.gated_state(d.line_probe(0, self.probe.unwrap_or(line)), contents)
}
}
impl<'a> AssumedParamHug<'a> {
pub(in crate::printer) fn new(
printer: &Printer<'_>,
arg: &'a ast::Expression<'a>,
) -> Option<Self> {
let flat = flat_sole_param_literal(printer, arg);
let return_hug = function_return_hug_break(printer, arg);
let body = match (&flat, &arg.kind) {
(Some(flat), _) => flat.body,
(None, ast::ExpressionKind::FunctionExpression(func)) if return_hug.is_some() => {
&func.body
}
_ => return None,
};
let is_arrow = flat.as_ref().is_some_and(|flat| flat.is_arrow);
let body_doc = shared_callback_body_doc(printer, arg, body, is_arrow);
Some(Self {
arg,
literal: flat.as_ref().map(|flat| flat.literal),
return_break: flat.and_then(|flat| flat.return_break),
return_hug,
body: (body.span.start, body_doc),
})
}
#[inline]
pub(super) fn body_inject(&self) -> (u32, DocId) {
self.body
}
#[inline]
pub(super) fn sharing<R>(
hug: Option<&Self>,
printer: &Printer<'_>,
build: impl FnOnce() -> R,
) -> R {
printer.with_arrow_body_inject(hug.map(Self::body_inject), build)
}
pub(super) fn build(
&self,
printer: &Printer<'_>,
flattens_params: bool,
build: impl Fn() -> DocId,
) -> AssumedHugStates {
let probe_break = self.return_probe(flattens_params);
let hug_break = self.return_hug.filter(|_| flattens_params);
debug_assert!(
probe_break.is_none() || hug_break.is_none() || probe_break == hug_break,
"the literal's probe and the return-type hug read one return type's `{{`"
);
let return_printed = probe_break
.or(hug_break)
.map(|assumed| self.print(printer, ShareTag::AssumedHugProbe, assumed, &build));
AssumedHugStates {
literal: self.literal.map(|literal| AssumedHugDoc {
doc: self.print(
printer,
ShareTag::AssumedHugArg,
AssumedBreak::TypeLiteral(literal),
&build,
),
probe: probe_break.and(return_printed),
}),
return_hug: hug_break.and(return_printed),
}
}
pub(in crate::printer) fn gate_line(
&self,
printer: &Printer<'_>,
flattens_params: bool,
build: impl Fn() -> DocId,
) -> DocId {
match self.return_probe(flattens_params) {
Some(assumed) => printer.with_assumed_break(assumed, || {
printer.with_arrow_body_inject(Some(self.body), build)
}),
None => build(),
}
}
#[inline]
fn return_probe(&self, flattens_params: bool) -> Option<AssumedBreak> {
self.return_break.filter(|_| flattens_params)
}
fn print(
&self,
printer: &Printer<'_>,
tag: ShareTag,
assumed: AssumedBreak,
build: impl Fn() -> DocId,
) -> DocId {
let key = printer.chain_share_key(self.arg, tag);
printer.chain_shared_doc(key, || {
printer.with_assumed_break(assumed, || {
printer.with_arrow_body_inject(Some(self.body), build)
})
})
}
pub(super) fn take_forced(
&self,
printer: &Printer<'_>,
kind: AssumedHugKind,
build: impl Fn() -> DocId,
) -> Option<DocId> {
if printer.force_assumed_param_hug.get() != Some((self.arg.span.start, kind)) {
return None;
}
let (tag, assumed) = match kind {
AssumedHugKind::ParamLiteral => (
ShareTag::AssumedHugArg,
AssumedBreak::TypeLiteral(self.literal?),
),
AssumedHugKind::ReturnType => (ShareTag::AssumedHugProbe, self.return_hug?),
};
printer.force_assumed_param_hug.set(None);
Some(self.print(printer, tag, assumed, build))
}
}
fn build_single_arrow_arg_states(
d: &DocArena,
opener: ArgOpener,
docs: HugArgDocs,
body_expands_internally: bool,
assumed: Option<AssumedHugDoc>,
) -> DocId {
if !body_expands_internally {
let inline = opener.inline(d, &[], docs.expanded);
let break_out = opener.break_out_state(d, docs.printed);
return match assumed {
Some(assumed) => opener.ladder(
d,
&[
inline,
assumed.state(d, docs.printed, opener.inline(d, &[], assumed.doc)),
break_out,
],
),
None => opener.ladder(d, &[inline, break_out]),
};
}
opener.inline_hug_or_expand_all(d, &[], docs.expanded, docs.printed)
}
#[derive(Clone, Copy)]
pub(super) struct ArgOpener {
callee: Option<DocId>,
open: DocId,
}
impl ArgOpener {
#[inline]
pub(super) fn callee(d: &DocArena, callee: DocId) -> Self {
Self {
callee: Some(callee),
open: d.text("("),
}
}
#[inline]
pub(super) fn chain(prefix: DocId) -> Self {
Self {
callee: None,
open: prefix,
}
}
#[inline]
pub(super) fn args_only(self) -> Self {
Self {
callee: None,
open: self.open,
}
}
#[inline]
pub(super) fn after_callee(self, d: &DocArena, layout: DocId) -> DocId {
match self.callee {
Some(callee) => d.concat(&[callee, layout]),
None => layout,
}
}
#[inline]
fn ladder(self, d: &DocArena, states: &[DocId]) -> DocId {
self.after_callee(d, d.conditional_group(states))
}
#[inline]
fn flat(self, d: &DocArena, before: DocId, after: DocId) -> DocId {
d.concat(&[self.open, before, after, d.text(")")])
}
#[inline]
pub(super) fn inline(self, d: &DocArena, head_parts: &[DocId], last_arg_doc: DocId) -> DocId {
self.flat(d, d.concat(head_parts), last_arg_doc)
}
#[inline]
fn hug(self, d: &DocArena, head_parts: &[DocId], last_arg_doc: DocId) -> DocId {
self.inline(d, head_parts, d.group_break(last_arg_doc))
}
#[inline]
fn break_out_state(self, d: &DocArena, printed: DocId) -> DocId {
d.concat(&[
self.open,
d.indent(d.concat(&[d.softline(), printed])),
d.softline(),
d.text(")"),
])
}
#[inline]
pub(super) fn break_out(self, d: &DocArena, printed: DocId) -> DocId {
self.after_callee(d, self.break_out_state(d, printed))
}
#[inline]
pub(super) fn lone_hug_ladder(
self,
d: &DocArena,
arg_doc: DocId,
params_render_flat: bool,
) -> DocId {
if params_render_flat {
self.inline_or_expand_all(d, &[], arg_doc, arg_doc)
} else {
self.inline_hug_or_expand_all(d, &[], arg_doc, arg_doc)
}
}
#[inline]
fn expand_all_state(self, d: &DocArena, all_args_broken: DocId) -> DocId {
d.group_break(d.concat(&[
self.open,
d.indent(d.concat(&[d.line(), all_args_broken])),
d.line(),
d.text(")"),
]))
}
#[inline]
pub(super) fn expand_all(self, d: &DocArena, all_args_broken: DocId) -> DocId {
self.after_callee(d, self.expand_all_state(d, all_args_broken))
}
#[inline]
pub(super) fn expand_all_if_head_breaks(
self,
d: &DocArena,
head_parts: &[DocId],
all_args_broken: DocId,
) -> Option<DocId> {
head_parts
.iter()
.any(|&id| d.will_break(id))
.then(|| self.expand_all(d, all_args_broken))
}
#[inline]
pub(super) fn wrap_soft(self, d: &DocArena, args: DocId) -> DocId {
self.after_callee(d, d.group(self.soft_list_contents(d, args)))
}
#[inline]
pub(super) fn soft_list(self, d: &DocArena, args: DocId) -> DocId {
self.after_callee(d, self.soft_list_contents(d, args))
}
#[inline]
fn soft_list_contents(self, d: &DocArena, args: DocId) -> DocId {
d.concat(&[
self.open,
d.indent_softline(args),
d.softline(),
d.text(")"),
])
}
#[inline]
pub(super) fn hug_arg(self, d: &DocArena, arg: DocId) -> DocId {
self.after_callee(
d,
d.group(d.concat(&[self.open, arg, d.softline(), d.text(")")])),
)
}
#[inline]
pub(super) fn inline_or_expand_all(
self,
d: &DocArena,
head_parts: &[DocId],
last_arg_doc: DocId,
all_args_broken: DocId,
) -> DocId {
self.ladder(
d,
&[
self.inline(d, head_parts, last_arg_doc),
self.expand_all_state(d, all_args_broken),
],
)
}
#[inline]
pub(super) fn inline_assumed_or_expand_all(
self,
d: &DocArena,
head_parts: &[DocId],
last_arg_doc: DocId,
assumed: AssumedHugStates,
all_args_broken: DocId,
) -> DocId {
let mut states: SmallVec<[DocId; 4]> = smallvec![self.inline(d, head_parts, last_arg_doc)];
if let Some(return_hug) = assumed.return_hug {
states.push(self.inline(d, head_parts, return_hug));
}
if let Some(literal) = assumed.literal {
states.push(literal.state(d, last_arg_doc, self.inline(d, head_parts, literal.doc)));
}
states.push(self.expand_all_state(d, all_args_broken));
self.ladder(d, &states)
}
#[inline]
pub(super) fn inline_hug_or_expand_all(
self,
d: &DocArena,
head_parts: &[DocId],
last_arg_doc: DocId,
all_args_broken: DocId,
) -> DocId {
if d.will_break(last_arg_doc) {
return self.ladder(
d,
&[
self.hug(d, head_parts, last_arg_doc),
self.expand_all_state(d, all_args_broken),
],
);
}
self.ladder(
d,
&[
self.inline(d, head_parts, last_arg_doc),
self.hug(d, head_parts, last_arg_doc),
self.expand_all_state(d, all_args_broken),
],
)
}
#[inline]
fn expand_first_state(self, d: &DocArena, first_arg_doc: DocId, tail: DocId) -> DocId {
self.flat(d, first_arg_doc, tail)
}
#[inline]
pub(super) fn inline_hug_first_or_expand_all(
self,
d: &DocArena,
first_arg_doc: DocId,
tail: DocId,
assumed: Option<AssumedHugDoc>,
all_args_broken: DocId,
) -> DocId {
let flat = self.expand_first_state(d, first_arg_doc, tail);
let hug = self.expand_first_state(d, d.group_break(first_arg_doc), tail);
match assumed {
Some(assumed) => {
let assumed_hug = self.expand_first_state(d, d.group_break(assumed.doc), tail);
let first_line = d.concat(&[first_arg_doc, d.text(",")]);
self.ladder(
d,
&[
flat,
hug,
assumed.state(d, first_line, assumed_hug),
all_args_broken,
],
)
}
None => self.ladder(d, &[flat, hug, all_args_broken]),
}
}
#[inline]
fn wrap_hard_broken(self, d: &DocArena, paren_line: &[DocId], args: DocId) -> DocId {
let mut parts: DocBuf = DocBuf::new();
parts.push(self.open);
parts.extend(paren_line.iter().copied());
parts.push(d.indent_hardline(args));
parts.push(d.hardline());
parts.push(d.text(")"));
self.after_callee(d, d.concat(&parts))
}
}
pub(super) fn build_expand_last_obj_array_doc(
printer: &Printer<'_>,
opener: ArgOpener,
obj_reuse: Option<(u32, DocId)>,
head_parts: &[DocId],
all_args_broken: DocId,
build: impl FnOnce() -> DocId,
) -> DocId {
let d = printer.d();
if let Some(bail) = opener.expand_all_if_head_breaks(d, head_parts, all_args_broken) {
return bail;
}
let expanded =
printer.with_arrow_body_inject(obj_reuse, || build_expand_last_arg_doc(printer, build));
opener.inline_hug_or_expand_all(d, head_parts, expanded, all_args_broken)
}
fn shared_callback_body_doc(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
body: &ast::BlockStatement<'_>,
is_arrow: bool,
) -> DocId {
let key = printer.chain_share_key(arg, ShareTag::AssumedHugBody);
printer.chain_shared_doc(key, || {
if is_arrow {
printer.arrow_block_body_doc(body)
} else {
printer.build_block_statement_doc(body)
}
})
}
fn prebuild_callback_block_body(
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
) -> Option<(u32, DocId)> {
let (body, is_arrow) = match &arg.kind {
ast::ExpressionKind::ArrowFunctionExpression(arrow) => match &arrow.body {
ast::ArrowFunctionBody::BlockStatement(block) => (block, true),
ast::ArrowFunctionBody::Expression(_) => return None,
},
ast::ExpressionKind::FunctionExpression(func) => (&func.body, false),
_ => return None,
};
Some((
body.span.start,
shared_callback_body_doc(printer, arg, body, is_arrow),
))
}
pub(super) fn build_expand_first_arg_doc(
printer: &Printer<'_>,
opener: ArgOpener,
arguments: &[&ast::Expression<'_>],
paren_open: u32,
call_end: u32,
) -> DocId {
let d = printer.d();
let build_first = || {
if matches!(
arguments[0].kind,
ast::ExpressionKind::ArrowFunctionExpression(_)
) {
build_flat_params_arg_doc(printer, || printer.build_arg_expression_doc(arguments[0]))
} else {
printer.build_arg_expression_doc(arguments[0])
}
};
let assumed = AssumedParamHug::new(printer, arguments[0]);
let inject = assumed.as_ref().map_or_else(
|| prebuild_callback_block_body(printer, arguments[0]),
|hug| Some(hug.body_inject()),
);
let first_arg_doc = printer.with_arrow_body_inject(inject, build_first);
let mut tail_parts = DocBuf::new();
let mut prev_end = arguments[0].span().end;
for arg in arguments.iter().skip(1) {
let (through_comma, leading) = build_arg_gap_docs(printer, prev_end, arg.span().start);
tail_parts.extend(through_comma);
tail_parts.push(d.text(" "));
tail_parts.extend(leading);
tail_parts.push(printer.build_arg_expression_doc(arg));
prev_end = arg.span().end;
}
if let Some(last_arg) = arguments.last() {
emit_last_arg_trailing_comments(
printer,
&mut tail_parts,
last_arg,
last_arg.paren_interior(),
call_end,
);
}
if tail_parts.iter().any(|&id| d.will_break(id)) {
return printer.with_arrow_body_inject(inject, || {
build_call_args_expanded(printer, opener, arguments, paren_open, call_end)
});
}
let all_args_broken = printer.with_arrow_body_inject(inject, || {
build_call_args_expanded(printer, opener.args_only(), arguments, paren_open, call_end)
});
if let Some(first) = assumed
.as_ref()
.and_then(|hug| hug.take_forced(printer, AssumedHugKind::ParamLiteral, build_first))
{
return opener.after_callee(
d,
opener.expand_first_state(d, d.group_break(first), d.concat(&tail_parts)),
);
}
let assumed = assumed.and_then(|hug| hug.build(printer, false, build_first).literal);
opener.inline_hug_first_or_expand_all(
d,
first_arg_doc,
d.concat(&tail_parts),
assumed,
all_args_broken,
)
}
pub(super) fn last_two_args_same_type(args: &[&ast::Expression<'_>]) -> bool {
let last = args[args.len() - 1];
let penultimate = args[args.len() - 2];
std::mem::discriminant(&last.kind) == std::mem::discriminant(&penultimate.kind)
}
#[inline]
fn build_break_body_state(
d: &DocArena,
opener: ArgOpener,
head_parts: &[DocId],
sig_doc: DocId,
body_doc: DocId,
conditional_body: bool,
) -> DocId {
d.concat(&[
opener.open,
d.concat(head_parts),
sig_doc,
d.text(arrow_before_broken_body(conditional_body)),
d.indent_hardline(body_doc),
d.hardline(),
d.text(")"),
])
}
#[inline]
const fn arrow_before_broken_body(conditional_body: bool) -> &'static str {
if conditional_body { " => " } else { " =>" }
}
#[expect(
clippy::too_many_arguments,
reason = "the ladder's pieces are each the caller's own; a struct would only rename them"
)]
pub(super) fn build_break_body_ladder(
d: &DocArena,
opener: ArgOpener,
head_parts: &[DocId],
sig_doc: Option<DocId>,
body_doc: DocId,
conditional_body: bool,
last_arg_doc: DocId,
all_args_broken: DocId,
) -> DocId {
let Some(sig_doc) = sig_doc else {
return opener.expand_all(d, all_args_broken);
};
let state_expand_all = opener.expand_all_state(d, all_args_broken);
if d.will_break(last_arg_doc) {
let state_hug = if d.will_break(body_doc) {
build_break_body_state(d, opener, head_parts, sig_doc, body_doc, conditional_body)
} else {
opener.hug(d, head_parts, last_arg_doc)
};
return opener.ladder(d, &[state_hug, state_expand_all]);
}
let state_break_body =
build_break_body_state(d, opener, head_parts, sig_doc, body_doc, conditional_body);
opener.ladder(
d,
&[
opener.inline(d, head_parts, last_arg_doc),
state_break_body,
state_expand_all,
],
)
}
pub(super) fn build_ternary_arrow_hug_ladder(
printer: &Printer<'_>,
opener: ArgOpener,
arrow: &ast::ArrowFunctionExpression<'_>,
body_expr: &ast::Expression<'_>,
sig_doc: DocId,
body_doc: DocId,
printed: impl FnOnce() -> DocId,
) -> DocId {
let d = printer.d();
let flat_pair = printer.arrow_body_takes_ternary_pair(body_expr);
let prefix = opener.open;
let body_start = body_expr.span().start;
let whole = printer.with_arrow_body_inject(Some((body_start, body_doc)), printed);
let state_all_broken = d.concat(&[
prefix,
d.indent(d.concat(&[d.hardline(), whole])),
d.hardline(),
d.text(")"),
]);
let body_below = prepend_arrow_body_comments(printer, arrow, body_start, body_doc);
let body_doc = prepend_hugged_arrow_body_comments(printer, arrow, body_start, body_doc);
let arrow_before = arrow_before_broken_body(
flat_pair && !arrow_body_has_leading_own_line_comment(printer, arrow, body_expr),
);
let arrow_broken = d.concat(&[sig_doc, d.text(arrow_before), d.indent_hardline(body_below)]);
let state_break = d.concat(&[prefix, arrow_broken, d.hardline(), d.text(")")]);
if d.will_break(body_doc) {
return opener.ladder(d, &[state_break, state_all_broken]);
}
let arrow_flat = if flat_pair {
d.concat(&[sig_doc, d.text(" => ("), body_doc, d.text(")")])
} else {
d.concat(&[sig_doc, d.text(" => "), body_doc])
};
let state_flat = d.concat(&[prefix, arrow_flat, d.text(")")]);
opener.ladder(d, &[state_flat, state_break, state_all_broken])
}
pub(super) fn build_arrow_call_body_states(
printer: &Printer<'_>,
opener: ArgOpener,
arg: &ast::Expression<'_>,
arrow: &ast::ArrowFunctionExpression<'_>,
body_expr: &ast::Expression<'_>,
body_reuse: (u32, DocId),
printed: impl FnOnce() -> DocId,
) -> DocId {
let d = printer.d();
let arrow_doc = printer.with_arrow_body_inject(Some(body_reuse), printed);
let (_, body_doc) = body_reuse;
let body_doc = prepend_arrow_body_comments(printer, arrow, body_expr.span().start, body_doc);
let sig_doc = build_hugged_arrow_sig_doc(printer, arrow, arg.span);
build_break_body_ladder(
d,
opener,
&[],
sig_doc,
body_doc,
false,
arrow_doc,
arrow_doc,
)
}
pub(super) fn build_args_joined_with_comments(
printer: &Printer<'_>,
arguments: &[&ast::Expression<'_>],
paren_open: u32,
paren_close: u32,
join: ArgsJoin,
paren_line: &mut DocBuf,
) -> DocId {
let d = printer.d();
let mut parts = DocBuf::new();
let use_hardline = join.use_hardline();
let first_arg_start = arguments[0].span().start;
if !matches!(join, ArgsJoin::HardlineLeadingGapEmitted) {
emit_first_arg_leading_comments(
printer,
paren_line,
&mut parts,
paren_open,
first_arg_start,
);
}
let no_comment_sep = if use_hardline {
d.comma_hardline()
} else {
d.comma_line()
};
let mut prev_gap_has_blank = false;
for (i, arg) in arguments.iter().enumerate() {
if prev_gap_has_blank
&& i > 0
&& !printer.has_comments_to_emit_between(arguments[i - 1].span().end, arg.span().start)
{
parts.push(d.literalline());
parts.push(d.hardline());
}
parts.push(build_joined_argument_doc(printer, paren_open, arguments, i));
if i < arguments.len() - 1 {
let next_arg_start = arguments[i + 1].span().start;
if printer.inter_arg_gap_has_comments(arg, next_arg_start) {
let interior = printer.arg_paren_interior(paren_open, arguments, i);
let gap = printer.open_inter_arg_gap(&mut parts, arg, interior, next_arg_start);
debug_assert!(!gap.forces_expansion || use_hardline);
if (join.preserves_blanks() || gap.comments.is_behind_interior())
&& gap.comments.has_blank_line_in_gap(printer)
{
parts.push(d.literalline());
}
parts.push(if gap.comments.has_trailing_line() || use_hardline {
d.hardline()
} else {
d.line()
});
gap.comments
.emit_leading_comments_inline_aware(&mut parts, printer);
prev_gap_has_blank = false;
} else if join.preserves_blanks() {
parts.push(d.text(","));
let arg_end = arg.span().end;
prev_gap_has_blank = printer
.is_next_line_empty(arg_end, printer.blank_scan_end(arg_end, next_arg_start));
if !prev_gap_has_blank {
parts.push(d.hardline());
}
} else {
parts.push(no_comment_sep);
}
} else {
let interior = printer.arg_paren_interior(paren_open, arguments, i);
emit_last_arg_trailing_comments(printer, &mut parts, arg, interior, paren_close);
}
}
d.concat(&parts)
}
pub(super) fn build_call_args_expanded(
printer: &Printer<'_>,
opener: ArgOpener,
arguments: &[&ast::Expression<'_>],
paren_open: u32,
paren_close: u32,
) -> DocId {
let mut paren_line = DocBuf::new();
let args = build_args_joined_with_comments(
printer,
arguments,
paren_open,
paren_close,
ArgsJoin::Hardline,
&mut paren_line,
);
opener.wrap_hard_broken(printer.d(), &paren_line, args)
}
#[derive(Clone, Copy)]
pub(super) enum ArgsJoin {
Hardline,
HardlineLeadingGapEmitted,
HardlinePreserveBlanks,
SoftLine,
}
impl ArgsJoin {
fn use_hardline(self) -> bool {
!matches!(self, Self::SoftLine)
}
fn preserves_blanks(self) -> bool {
matches!(self, Self::HardlinePreserveBlanks)
}
}
pub(super) fn build_joined_argument_doc(
printer: &Printer<'_>,
paren_open: u32,
args: &[&ast::Expression<'_>],
i: usize,
) -> DocId {
build_printed_argument_doc(printer, args[i], || {
printer.build_arg_item_doc(paren_open, args, i)
})
}
pub(super) fn should_expand_first_arg(
printer: &Printer<'_>,
args: &[&ast::Expression<'_>],
) -> bool {
if args.len() != 2 {
return false;
}
if !is_block_function(args[0]) {
return false;
}
is_short_second_arg_for_expand_first(args[1], args[0].span().end, |start, end| {
printer.has_comments_on_page_between(start, end)
})
}
pub(super) fn first_arg_signature_refuses_expand_first(
printer: &Printer<'_>,
args: &[&ast::Expression<'_>],
) -> bool {
matches!(
args.first(),
Some(ast::Expression { span, kind: ast::ExpressionKind::ArrowFunctionExpression(arrow) })
if arrow_signature_refuses_hug(printer, arrow, *span)
|| printer.arrow_empty_params_hold_comments(arrow)
)
}
pub(super) fn append_type_args_with_gap_comments(
printer: &Printer<'_>,
callee: DocId,
callee_end: u32,
type_arguments: Option<&ast::TSTypeParameterInstantiation<'_>>,
) -> DocId {
let d = printer.d();
match type_arguments {
Some(ta) => {
let ta_doc = printer.build_type_parameter_instantiation_doc(ta);
match printer.build_name_to_type_params_comments_opt(
callee_end,
ta.span.start,
CommentSpacing::Trailing,
) {
Some(comments_doc) => d.concat(&[callee, comments_doc, ta_doc]),
None => d.concat(&[callee, ta_doc]),
}
}
None => callee,
}
}
pub(super) fn build_empty_args_doc(
printer: &Printer<'_>,
callee: DocId,
after_type_args: u32,
paren_close: u32,
optional: bool,
) -> DocId {
let d = printer.d();
let prefix = if optional { d.text("?.(") } else { d.text("(") };
d.concat(&[
callee,
build_empty_args_parens_doc(printer, after_type_args, paren_close, prefix),
])
}
pub(super) fn multiline_template_hug_applies(
printer: &Printer<'_>,
args: &[&ast::Expression<'_>],
gap_start: u32,
) -> bool {
is_sole_multiline_template_arg(args)
&& !sole_template_on_its_own_line(
printer,
gap_start,
args[0].span().start,
)
&& printer.args_frozen_span(gap_start, args, 0).is_none()
}
pub(in crate::printer) fn sole_template_on_its_own_line(
printer: &Printer<'_>,
gap_start: u32,
template_start: u32,
) -> bool {
let authored = has_newline_before_position(printer.source, template_start);
if !printer.has_comments_on_page_between(gap_start, template_start) {
return authored;
}
sole_template_on_its_own_line_commented(printer, gap_start, template_start, authored)
}
#[inline(never)]
fn sole_template_on_its_own_line_commented(
printer: &Printer<'_>,
gap_start: u32,
template_start: u32,
authored: bool,
) -> bool {
use crate::printer::comments::{LeadingGlue, LeadingSeparator};
let owned_last = printer
.comments_on_page_between(gap_start, template_start)
.last()
.is_some_and(|c| c.owned_by_node);
let (hug_glued, list_glued) = if owned_last {
(true, true)
} else {
let list_glued = printer
.leading_run_tail_separator(
printer.comments_to_emit_between(gap_start, template_start),
template_start,
LeadingGlue::AdjacentStrippedParen,
)
.is_some_and(|(separator, _)| separator == LeadingSeparator::Space);
let hug_glued = printer.rhs_comments_glued_tail_separator(gap_start, template_start)
== Some(LeadingSeparator::Space);
(hug_glued, list_glued)
};
if authored {
!(list_glued && hug_glued)
} else {
!hug_glued && !list_glued
}
}
#[inline]
pub(super) fn is_sole_multiline_template_arg(args: &[&ast::Expression<'_>]) -> bool {
matches!(args, [arg] if is_multiline_template_expression(arg))
}
pub(super) fn try_hug_multiline_template_arg(
printer: &Printer<'_>,
callee: DocId,
args: &[&ast::Expression<'_>],
gap_start: u32,
paren_close: u32,
) -> Option<DocId> {
if !multiline_template_hug_applies(printer, args, gap_start) {
return None;
}
let template_start = args[0].span().start;
let d = printer.d();
let arg_doc = printer.build_arg_expression_doc(args[0]);
let mut parts: DocBuf = smallvec![callee, d.text("(")];
if let Some(leading) = printer.build_rhs_comments_glued_opt(gap_start, template_start) {
parts.push(leading);
}
parts.push(arg_doc);
printer.push_trailing_comments_in_range(&mut parts, args[0].span().end, paren_close);
parts.push(d.text(")"));
Some(d.concat(&parts))
}
pub(super) fn build_call_args_with_blank_lines(
printer: &Printer<'_>,
callee: DocId,
args: &[&ast::Expression<'_>],
paren_open: u32,
paren_close: u32,
) -> DocId {
let mut paren_line = DocBuf::new();
let args_doc = build_args_joined_with_comments(
printer,
args,
paren_open,
paren_close,
ArgsJoin::HardlinePreserveBlanks,
&mut paren_line,
);
wrap_call_with_hard_breaks_paren_line(printer.d(), callee, &paren_line, args_doc)
}