use super::arg_comments::{any_comment_forces_expansion_slice, last_arg_has_comments};
use super::arg_predicates::{
arrow_body_breaks_after_space, arrow_body_is_call_through_non_null,
is_array_or_object_unwrapped, is_ternary_arrow_body,
};
use super::arg_wrapping::{
ArgOpener, AssumedParamHug, arrow_body_expands_internally, arrow_hug_refused_by_comments,
build_args_split_last, build_arrow_gap_break_multi_arg_doc, build_break_body_ladder,
build_expand_last_obj_array_doc, build_hugged_arrow_sig_doc, build_joined_argument_doc,
could_expand_arrow_chain, last_arg_arrow_gap_break, last_two_args_same_type,
prebuild_expand_last_break_body, prebuild_expand_last_obj_array_body,
prepend_arrow_body_comments,
};
use crate::ast;
use crate::printer::Printer;
use crate::printer::expressions::functions::{
arrow_signature_refuses_hug, callback_signature_refuses_hug,
};
use tsv_lang::doc::arena::DocId;
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum ArgOwner {
Call,
New,
}
impl ArgOwner {
fn callback_signature_refuses_hug(
self,
printer: &Printer<'_>,
arg: &ast::Expression<'_>,
arg_count: usize,
) -> bool {
match self {
Self::Call => callback_signature_refuses_hug(printer, arg, arg_count),
Self::New => match &arg.kind {
ast::ExpressionKind::ArrowFunctionExpression(arrow) => {
arrow_signature_refuses_hug(printer, arrow, arg.span)
}
_ => false,
},
}
}
}
pub(super) fn try_expand_last_arg(
printer: &Printer<'_>,
arguments: &[&ast::Expression<'_>],
callee: DocId,
paren_open: u32,
call_end: u32,
has_comments: bool,
owner: ArgOwner,
) -> Option<DocId> {
let d = printer.d();
if arguments.len() < 2 {
return None;
}
let opener = ArgOpener::callee(d, callee);
let last_arg = arguments.last().copied();
let last_is_function = matches!(
last_arg,
Some(ast::Expression {
kind: ast::ExpressionKind::ArrowFunctionExpression(_)
| ast::ExpressionKind::FunctionExpression(_),
..
})
);
let last_is_collection = last_arg.is_some_and(|arg| {
is_array_or_object_unwrapped(arg, printer) && !printer.arg_is_concisely_printed_array(arg)
});
if !(last_is_function || last_is_collection) {
return None;
}
let hug_eligible = match last_arg {
Some(ast::Expression {
span: arrow_span,
kind: ast::ExpressionKind::ArrowFunctionExpression(arrow),
}) => {
could_expand_arrow_chain(arrow)
|| match &arrow.body {
ast::ArrowFunctionBody::Expression(body_expr) => {
(arrow_body_is_call_through_non_null(body_expr)
|| is_ternary_arrow_body(body_expr))
&& !(has_comments
&& arrow_hug_refused_by_comments(
printer,
arrow,
*arrow_span,
body_expr,
))
}
ast::ArrowFunctionBody::BlockStatement(_) => false,
}
}
_ => true,
};
let arrow_gap_break = if has_comments {
last_arg.and_then(|last| last_arg_arrow_gap_break(printer, last))
} else {
None
};
if !(hug_eligible || arrow_gap_break.is_some()) {
return None;
}
if arguments.len() == 2
&& matches!(
arguments.first(),
Some(ast::Expression {
kind: ast::ExpressionKind::ArrowFunctionExpression(_),
..
})
)
&& matches!(
last_arg,
Some(ast::Expression {
kind: ast::ExpressionKind::ArrayExpression(_),
..
})
)
{
return None;
}
if has_comments && any_comment_forces_expansion_slice(arguments, printer, paren_open, call_end)
{
return None;
}
if has_comments && last_arg_has_comments(arguments, printer, call_end, paren_open) {
return None;
}
let (body_reuse, obj_reuse) = if arrow_gap_break.is_some() {
(None, None)
} else {
let call_body = prebuild_expand_last_break_body(printer, last_arg);
let obj_body = if call_body.is_none() {
prebuild_expand_last_obj_array_body(printer, last_arg)
} else {
None
};
(call_body, obj_body)
};
let assumed = if arrow_gap_break.is_none() && body_reuse.is_none() && obj_reuse.is_none() {
last_arg.and_then(|last| AssumedParamHug::new(printer, last))
} else {
None
};
let inject = match &arrow_gap_break {
Some(gap_break) => gap_break.inject,
None => body_reuse
.or(obj_reuse)
.or_else(|| assumed.as_ref().map(AssumedParamHug::body_inject)),
};
let (head_parts, last_arg_doc, all_args_broken) = printer
.with_arrow_body_inject(inject, || {
build_args_split_last(arguments, printer, paren_open, has_comments)
});
if let Some(bail) = opener.expand_all_if_head_breaks(d, &head_parts, all_args_broken) {
return Some(bail);
}
if arrow_gap_break.is_some()
&& let Some(last) = last_arg
{
return Some(build_arrow_gap_break_multi_arg_doc(
printer,
opener,
inject,
&head_parts,
last_arg_doc,
all_args_broken,
|| printer.build_expression_doc(last),
));
}
if last_is_function {
if let Some(ast::Expression { span: arrow_span, 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(_)))
&& !(has_comments && arrow_hug_refused_by_comments(printer, arrow, *arrow_span, body_expr))
{
let sig_doc = build_hugged_arrow_sig_doc(printer, arrow, *arrow_span);
let body_doc = body_reuse.map_or_else(
|| printer.build_arrow_arg_body_doc(body_expr),
|(_, doc)| doc,
);
let body_doc =
prepend_arrow_body_comments(printer, arrow, body_expr.span().start, body_doc);
return Some(build_break_body_ladder(
d,
opener,
&head_parts,
sig_doc,
body_doc,
arrow_body_breaks_after_space(printer, arrow, body_expr),
last_arg_doc,
all_args_broken,
));
}
if let Some(arg) = last_arg
&& let ast::ExpressionKind::ArrowFunctionExpression(arrow) = &arg.kind
&& arrow_body_expands_internally(arrow)
&& !arrow_signature_refuses_hug(printer, arrow, arg.span)
{
return Some(build_expand_last_obj_array_doc(
printer,
opener,
obj_reuse,
&head_parts,
all_args_broken,
|| printer.build_expression_doc(arg),
));
}
if last_arg
.is_some_and(|arg| owner.callback_signature_refuses_hug(printer, arg, arguments.len()))
{
return Some(opener.expand_all(d, all_args_broken));
}
let assumed = assumed.map_or_else(Default::default, |hug| {
let flattens_params = owner == ArgOwner::Call && arguments.len() > 1;
hug.build(printer, flattens_params, || {
build_joined_argument_doc(printer, paren_open, arguments, arguments.len() - 1)
})
});
return Some(opener.inline_assumed_or_expand_all(
d,
&head_parts,
last_arg_doc,
assumed,
all_args_broken,
));
}
if last_two_args_same_type(arguments) {
if d.will_break(last_arg_doc) {
return Some(opener.expand_all(d, all_args_broken));
}
return Some(opener.inline_or_expand_all(d, &head_parts, last_arg_doc, all_args_broken));
}
Some(opener.inline_hug_or_expand_all(d, &head_parts, last_arg_doc, all_args_broken))
}