#![expect(clippy::literal_string_with_formatting_args)]
use crate::ast::{self, Fragment, FragmentNode};
use crate::printer::{HeadExpr, Printer};
use smallvec::smallvec;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::DocId;
use super::fragment_doc::LeadingEdge;
use super::helpers::{HeadTail, each_expr_comment_end};
pub(crate) const IF_BLOCK_OPEN: &str = "{#if ";
pub(crate) const ELSE_IF_BLOCK_OPEN: &str = "{:else if ";
pub(crate) const EACH_BLOCK_OPEN: &str = "{#each ";
pub(crate) const AWAIT_BLOCK_OPEN: &str = "{#await ";
pub(crate) const KEY_BLOCK_OPEN: &str = "{#key ";
enum IfPiece {
Consequent(DocId),
ElseIf { head: DocId, body: DocId },
Else(DocId),
}
pub(super) struct AwaitPieces {
pub(super) pending: Option<DocId>,
pub(super) then_kw: Option<DocId>,
pub(super) then_body: Option<DocId>,
pub(super) catch_kw: Option<DocId>,
pub(super) catch_body: Option<DocId>,
}
fn ends_at_base_closer(expr: &tsv_ts::ast::Expression<'_>) -> bool {
use tsv_ts::ast::ArrowFunctionBody;
use tsv_ts::ast::ExpressionKind as E;
fn callee_has_no_call(mut e: &tsv_ts::ast::Expression<'_>) -> bool {
loop {
match &e.kind {
E::Identifier(_) | E::ThisExpression(_) | E::Super(_) => return true,
E::MemberExpression(m) => e = m.object,
_ => return false,
}
}
}
match &expr.kind {
E::CallExpression(c) => callee_has_no_call(c.callee),
E::NewExpression(n) => callee_has_no_call(n.callee),
E::ImportExpression(_) => true,
E::ObjectExpression(_)
| E::ArrayExpression(_)
| E::FunctionExpression(_)
| E::ClassExpression(_) => true,
E::ArrowFunctionExpression(a) => matches!(a.body, ArrowFunctionBody::BlockStatement(_)),
E::UnaryExpression(u) => ends_at_base_closer(u.argument),
E::AwaitExpression(a) => ends_at_base_closer(a.argument),
E::TSTypeAssertion(t) => ends_at_base_closer(t.expression),
E::TSNonNullExpression(t) => ends_at_base_closer(t.expression),
_ => false,
}
}
#[derive(Clone, Copy)]
enum HeadCloser {
BlockTag(Option<DocId>),
EachKey,
}
impl HeadCloser {
fn parts(self) -> (&'static str, Option<DocId>) {
match self {
Self::BlockTag(clause) => ("}", clause),
Self::EachKey => (")", None),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum SectionWhitespace {
Trimmed,
Preserved,
}
impl SectionWhitespace {
fn renders_nothing(self, fragment: &Fragment<'_>) -> bool {
match self {
Self::Trimmed => fragment
.nodes
.iter()
.all(FragmentNode::is_whitespace_only_text),
Self::Preserved => fragment.nodes.is_empty(),
}
}
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(super) enum AwaitShorthand {
Then,
Catch,
None,
}
pub(super) struct AwaitSections<'b, 'a> {
pub(super) shorthand: AwaitShorthand,
pub(super) pending: Option<&'b Fragment<'a>>,
pub(super) then: Option<&'b Fragment<'a>>,
pub(super) catch: Option<&'b Fragment<'a>>,
}
impl<'b, 'a> AwaitSections<'b, 'a> {
pub(super) fn classify(block: &'b ast::AwaitBlock<'a>, ws: SectionWhitespace) -> Self {
let shorthand = await_shorthand(block, ws);
Self {
shorthand,
pending: block
.pending
.as_ref()
.filter(|_| shorthand == AwaitShorthand::None),
then: block.then.as_ref().filter(|_| then_has_content(block, ws)),
catch: block.catch.as_ref(),
}
}
}
fn then_has_content(block: &ast::AwaitBlock<'_>, ws: SectionWhitespace) -> bool {
block.then.as_ref().is_some_and(|t| !ws.renders_nothing(t))
}
fn await_shorthand(block: &ast::AwaitBlock<'_>, ws: SectionWhitespace) -> AwaitShorthand {
let has_pending_body = block.pending.as_ref().is_some_and(|p| {
!ws.renders_nothing(p) || p.nodes.iter().any(FragmentNode::is_boundary_break)
});
if has_pending_body {
AwaitShorthand::None
} else if then_has_content(block, ws) {
AwaitShorthand::Then
} else if block.error.is_some() || block.catch.is_some() {
AwaitShorthand::Catch
} else {
AwaitShorthand::None
}
}
pub(super) fn await_expr_comment_end(
block: &ast::AwaitBlock<'_>,
shorthand: AwaitShorthand,
) -> u32 {
let head_end = block.opening_tag_span.end - 1;
match shorthand {
AwaitShorthand::Then => block.value.as_ref().map_or(head_end, |v| v.span().start),
AwaitShorthand::Catch => block.error.as_ref().map_or(head_end, |e| e.span().start),
AwaitShorthand::None => head_end,
}
.min(head_end)
}
impl<'a> Printer<'a> {
fn block_head_can_wrap(&self, allow_wrapping: bool, in_multiline_context: bool) -> bool {
(allow_wrapping || in_multiline_context) && self.block_dangle_allowed()
}
fn build_block_head(
&self,
open: &'static str,
expr: &tsv_ts::ast::Expression<'_>,
head: HeadExpr,
closer: HeadCloser,
can_wrap: bool,
) -> DocId {
let d = self.d();
let HeadExpr {
doc: expr_doc,
layout,
ends_with_line_comment,
owes_continuation_indent: _,
} = head;
let hug = ends_at_base_closer(expr) && !layout.opens_own_line();
let open_doc = self.head_open_doc(open, layout.opens_own_line());
let (close_text, clause) = closer.parts();
let close = d.text(close_text);
if ends_with_line_comment {
let head = if can_wrap {
d.group(expr_doc)
} else {
expr_doc
};
return match clause {
Some(c) => d.concat(&[open_doc, head, c, close]),
None => d.concat(&[open_doc, head, close]),
};
}
if can_wrap {
if hug {
let grouped = d.group(expr_doc);
match clause {
Some(c) => {
let space = d.text(" ");
d.concat(&[open_doc, grouped, space, c, close])
}
None => d.concat(&[open_doc, grouped, close]),
}
} else {
let head_group = d.group_with_id(expr_doc);
let hardline = d.hardline();
let (break_tail, flat_tail) = match clause {
Some(c) => {
let space = d.text(" ");
(d.concat(&[hardline, c]), d.concat(&[space, c]))
}
None => (hardline, d.empty()),
};
let dangle = d.if_break_with_id(break_tail, flat_tail, head_group);
d.concat(&[open_doc, head_group.doc(), dangle, close])
}
} else {
match clause {
Some(c) => {
let space = d.text(" ");
d.concat(&[open_doc, expr_doc, space, c, close])
}
None => d.concat(&[open_doc, expr_doc, close]),
}
}
}
fn build_expanding_block(
&self,
head_doc: DocId,
body_doc: DocId,
close: DocId,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let lead = d.hardline();
let trail = d.hardline();
let multiline_tail = d.concat(&[d.indent(d.concat(&[lead, body_doc])), trail, close]);
let inline_tail = d.concat(&[d.indent(body_doc), close]);
self.build_expanding_construct(head_doc, false, inline_tail, multiline_tail, gt_prefix)
}
fn fold_gt(&self, gt_prefix: Option<DocId>, body: DocId) -> DocId {
let d = self.d();
match gt_prefix {
Some(gt) => d.concat(&[d.hardline(), gt, body]),
None => body,
}
}
fn dangle_gt(&self, gt_prefix: Option<DocId>, doc: DocId) -> DocId {
let d = self.d();
match gt_prefix {
None => doc,
Some(gt) if d.will_break(doc) => d.concat(&[d.hardline(), gt, doc]),
Some(gt) if d.can_break(doc) => {
let folded = d.if_break(d.concat(&[d.hardline(), gt]), gt);
d.group(d.concat(&[folded, doc]))
}
Some(gt) => d.concat(&[gt, doc]),
}
}
fn build_expanding_construct(
&self,
head_doc: DocId,
head_forced_break: bool,
inline_tail: DocId,
multiline_tail: DocId,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
if head_forced_break || d.will_break(head_doc) || d.will_break(inline_tail) {
return self.fold_gt(gt_prefix, d.concat(&[head_doc, multiline_tail]));
}
let inline = d.concat(&[head_doc, inline_tail]);
let expanded = d.concat(&[head_doc, multiline_tail]);
let Some(gt) = gt_prefix else {
return d.conditional_group(&[inline, expanded]);
};
let hugged = d.concat(&[gt, inline]);
let dangled = d.concat(&[d.hardline(), gt, d.conditional_group(&[inline, expanded])]);
d.conditional_group(&[hugged, dangled])
}
fn if_branches_all_inline(&self, block: &ast::IfBlock<'_>) -> bool {
let mut all_inline = self.fragment_inline_authored(&block.consequent);
let mut alt = block.alternate.as_ref();
while let Some(a) = alt {
if let Some(else_if) = Self::get_flattenable_else_if(a) {
all_inline &= self.fragment_inline_authored(&else_if.consequent);
alt = else_if.alternate.as_ref();
} else {
all_inline &= self.fragment_inline_authored(a);
alt = None;
}
}
all_inline
}
fn indent_body_expand(&self, body: DocId, multiline: bool) -> DocId {
let d = self.d();
if multiline {
d.indent_hardline(body)
} else {
d.indent(body)
}
}
fn build_if_pieces(&self, block: &ast::IfBlock<'_>) -> Vec<IfPiece> {
let mut pieces = vec![IfPiece::Consequent(
self.build_section_body_doc(&block.consequent),
)];
let mut alt = block.alternate.as_ref();
while let Some(a) = alt {
if let Some(else_if) = Self::get_flattenable_else_if(a) {
let head_expr = self.build_else_if_expr_doc(else_if, true);
let head = self.build_block_head(
ELSE_IF_BLOCK_OPEN,
else_if.test,
head_expr,
HeadCloser::BlockTag(None),
self.block_dangle_allowed(),
);
let body = self.build_section_body_doc(&else_if.consequent);
pieces.push(IfPiece::ElseIf { head, body });
alt = else_if.alternate.as_ref();
} else {
pieces.push(IfPiece::Else(self.build_section_body_doc(a)));
alt = None;
}
}
pieces
}
fn compose_if_tail(&self, pieces: &[IfPiece], multiline: bool) -> DocId {
let d = self.d();
let mut parts: DocBuf = DocBuf::new();
for piece in pieces {
match piece {
IfPiece::Consequent(body) => parts.push(self.indent_body_expand(*body, multiline)),
IfPiece::ElseIf { head, body } => {
if multiline {
parts.push(d.hardline());
}
parts.push(*head);
parts.push(self.indent_body_expand(*body, multiline));
}
IfPiece::Else(body) => {
if multiline {
parts.push(d.hardline());
}
parts.push(d.text("{:else}"));
parts.push(self.indent_body_expand(*body, multiline));
}
}
}
if multiline {
parts.push(d.hardline());
}
parts.push(d.text("{/if}"));
d.concat(&parts)
}
pub(super) fn build_if_block_doc_with_full_context(
&self,
block: &ast::IfBlock<'_>,
in_multiline_context: bool,
has_preceding_breakable: bool,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let allow_wrapping = !has_preceding_breakable;
let head = self.build_block_head_expr(
IF_BLOCK_OPEN,
block.opening_tag_span,
block.test,
block.opening_tag_span.end - 1,
allow_wrapping || in_multiline_context,
HeadTail::Delimiter,
);
let can_wrap = self.block_head_can_wrap(allow_wrapping, in_multiline_context);
let head_doc = self.build_block_head(
IF_BLOCK_OPEN,
block.test,
head,
HeadCloser::BlockTag(None),
can_wrap,
);
if self.if_branches_all_inline(block) {
let pieces = self.build_if_pieces(block);
let inline_tail = self.compose_if_tail(&pieces, false);
let multiline_tail = self.compose_if_tail(&pieces, true);
return self.build_expanding_construct(
head_doc,
false,
inline_tail,
multiline_tail,
gt_prefix,
);
}
let body_doc = self.build_nodes_doc_multiline(block.consequent.nodes);
let indented_body = self.indent_body_expand(body_doc, true);
let mut parts: DocBuf = smallvec![head_doc, indented_body];
if let Some(alt) = &block.alternate {
parts.push(d.hardline());
parts.push(self.build_if_alternate_doc(alt, in_multiline_context));
}
parts.push(d.hardline());
parts.push(d.text("{/if}"));
self.dangle_gt(gt_prefix, d.concat(&parts))
}
pub(super) fn get_flattenable_else_if<'arena>(
alt: &Fragment<'arena>,
) -> Option<&'arena ast::IfBlock<'arena>> {
let mut if_block: Option<&'arena ast::IfBlock<'arena>> = None;
for node in alt.nodes {
match node {
FragmentNode::IfBlock(b) => {
if if_block.is_some() {
return None;
}
if_block = Some(b);
}
FragmentNode::Text(t) if t.is_collapsible_ws_only => {
}
_ => {
return None;
}
}
}
if_block.filter(|b| b.elseif)
}
pub(super) fn build_else_if_expr_doc(
&self,
else_if: &ast::IfBlock<'_>,
in_multiline_context: bool,
) -> HeadExpr {
self.build_block_head_expr(
ELSE_IF_BLOCK_OPEN,
else_if.opening_tag_span,
else_if.test,
else_if.opening_tag_span.end - 1,
in_multiline_context,
HeadTail::Delimiter,
)
}
fn build_if_alternate_doc(&self, alt: &Fragment<'_>, in_multiline_context: bool) -> DocId {
let d = self.d();
if let Some(else_if) = Self::get_flattenable_else_if(alt) {
let head_expr = self.build_else_if_expr_doc(else_if, in_multiline_context);
let body_doc = self.build_nodes_doc_multiline(else_if.consequent.nodes);
let indented_body = self.indent_body_expand(body_doc, true);
let head_doc = self.build_block_head(
ELSE_IF_BLOCK_OPEN,
else_if.test,
head_expr,
HeadCloser::BlockTag(None),
in_multiline_context && self.block_dangle_allowed(),
);
let mut parts: DocBuf = smallvec![head_doc, indented_body];
if let Some(nested_alt) = &else_if.alternate {
parts.push(d.hardline());
parts.push(self.build_if_alternate_doc(nested_alt, in_multiline_context));
}
return d.concat(&parts);
}
let body_doc = self.build_nodes_doc_multiline(alt.nodes);
let indented_body = self.indent_body_expand(body_doc, true);
d.concat(&[d.text("{:else}"), indented_body])
}
fn each_branches_all_inline(&self, block: &ast::EachBlock<'_>) -> bool {
let mut all_inline = self.fragment_inline_authored(&block.body);
if let Some(fallback) = &block.fallback {
all_inline &= self.fragment_inline_authored(fallback);
}
all_inline
}
fn build_each_pieces(&self, block: &ast::EachBlock<'_>) -> (DocId, Option<DocId>) {
let body = self.build_section_body_doc(&block.body);
let fallback = block
.fallback
.as_ref()
.map(|f| self.build_section_body_doc(f));
(body, fallback)
}
fn compose_each_tail(&self, body: DocId, fallback: Option<DocId>, multiline: bool) -> DocId {
let d = self.d();
let mut parts: DocBuf = DocBuf::new();
parts.push(self.indent_body_expand(body, multiline));
if let Some(fb) = fallback {
if multiline {
parts.push(d.hardline());
}
parts.push(d.text("{:else}"));
parts.push(self.indent_body_expand(fb, multiline));
}
if multiline {
parts.push(d.hardline());
}
parts.push(d.text("{/each}"));
d.concat(&parts)
}
fn build_each_key_doc(
&self,
block: &ast::EachBlock<'_>,
allow_wrapping: bool,
in_multiline_context: bool,
) -> Option<DocId> {
let key = block.key.as_ref()?;
let key_head = self.build_each_key_expr(key, allow_wrapping || in_multiline_context);
if key_head.layout.opens_own_line() || block.context.is_none() {
return Some(self.build_prefixed_head_doc("(", key_head, self.d().text(")")));
}
let can_wrap = self.block_head_can_wrap(allow_wrapping, in_multiline_context);
Some(self.build_block_head("(", key.expression, key_head, HeadCloser::EachKey, can_wrap))
}
pub(super) fn build_each_block_doc_with_full_context(
&self,
block: &ast::EachBlock<'_>,
in_multiline_context: bool,
has_preceding_breakable: bool,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let allow_wrapping = !has_preceding_breakable;
let expr_comment_end = each_expr_comment_end(block);
let head = self.build_block_head_expr(
EACH_BLOCK_OPEN,
block.opening_tag_span,
block.expression,
expr_comment_end,
allow_wrapping || in_multiline_context,
HeadTail::of_each(block),
);
let key_doc = self.build_each_key_doc(block, allow_wrapping, in_multiline_context);
let key_forced_break =
block.context.is_some() && key_doc.is_some_and(|kd| d.will_break(kd));
let mut clause_forced_break = key_forced_break;
let (head_expr, clause) = if let Some(context) = block.context {
let mut clause_parts: DocBuf = smallvec![d.text("as ")];
let pattern_doc = self.build_block_pattern_doc(context);
clause_forced_break |= d.will_break(pattern_doc);
clause_parts.push(pattern_doc);
if let Some(index) = block.index {
clause_parts.push(d.text(", "));
clause_parts.push(d.text_pooled(index));
}
if let Some(kd) = key_doc {
clause_parts.push(d.text(" "));
clause_parts.push(kd);
}
(head, Some(d.concat(&clause_parts)))
} else {
let mut e: DocBuf = smallvec![head.doc];
if let Some(index) = block.index {
e.push(d.text(", "));
e.push(d.text_pooled(index));
}
if let Some(kd) = key_doc {
e.push(d.text(" "));
e.push(kd);
}
(
HeadExpr {
doc: d.concat(&e),
..head
},
None,
)
};
let can_wrap = self.block_head_can_wrap(allow_wrapping, in_multiline_context);
let head_doc = self.build_block_head(
EACH_BLOCK_OPEN,
block.expression,
head_expr,
HeadCloser::BlockTag(clause),
can_wrap,
);
if self.each_branches_all_inline(block) {
let (body, fallback) = self.build_each_pieces(block);
let inline_tail = self.compose_each_tail(body, fallback, false);
let multiline_tail = self.compose_each_tail(body, fallback, true);
return self.build_expanding_construct(
head_doc,
clause_forced_break,
inline_tail,
multiline_tail,
gt_prefix,
);
}
let body_doc = self.build_nodes_doc_multiline(block.body.nodes);
let indented_body = self.indent_body_expand(body_doc, true);
let mut parts: DocBuf = smallvec![head_doc, indented_body];
if let Some(fallback) = &block.fallback {
parts.push(d.hardline());
parts.push(d.text("{:else}"));
let fallback_doc = self.build_nodes_doc_multiline(fallback.nodes);
parts.push(self.indent_body_expand(fallback_doc, true));
}
parts.push(d.hardline());
parts.push(d.text("{/each}"));
self.dangle_gt(gt_prefix, d.concat(&parts))
}
fn fragment_inline_authored(&self, frag: &Fragment<'_>) -> bool {
!self.fragment_boundary_newline(frag, true)
&& !self.fragment_boundary_newline(frag, false)
&& !self.fragment_should_force_break_content(frag.nodes)
}
fn build_section_body_doc(&self, fragment: &Fragment<'_>) -> DocId {
self.build_nodes_doc_trimmed(fragment.nodes, false, LeadingEdge::Trimmed)
}
fn await_then_keyword(&self, block: &ast::AwaitBlock<'_>) -> DocId {
let d = self.d();
match block.value {
Some(value) => d.concat(&[
d.text("{:then "),
self.build_block_pattern_doc(value),
d.text("}"),
]),
None => d.text("{:then}"),
}
}
fn await_catch_keyword(&self, block: &ast::AwaitBlock<'_>) -> Option<DocId> {
let d = self.d();
if let Some(error) = block.error {
Some(d.concat(&[
d.text("{:catch "),
self.build_block_pattern_doc(error),
d.text("}"),
]))
} else if block.catch.is_some() {
Some(d.text("{:catch}"))
} else {
None
}
}
pub(super) fn build_await_clause(
&self,
block: &ast::AwaitBlock<'_>,
shorthand: AwaitShorthand,
) -> Option<DocId> {
let d = self.d();
match shorthand {
AwaitShorthand::Then => Some(match block.value {
Some(value) => d.concat(&[d.text("then "), self.build_block_pattern_doc(value)]),
None => d.text("then"),
}),
AwaitShorthand::Catch => Some(match block.error {
Some(error) => d.concat(&[d.text("catch "), self.build_block_pattern_doc(error)]),
None => d.text("catch"),
}),
AwaitShorthand::None => None,
}
}
pub(super) fn build_await_pieces(
&self,
block: &ast::AwaitBlock<'_>,
sections: &AwaitSections<'_, '_>,
body: impl Fn(&Fragment<'_>) -> DocId,
) -> AwaitPieces {
let shorthand = sections.shorthand;
AwaitPieces {
pending: sections.pending.map(&body),
then_kw: sections
.then
.filter(|_| shorthand != AwaitShorthand::Then)
.map(|_| self.await_then_keyword(block)),
then_body: sections.then.map(&body),
catch_kw: (shorthand != AwaitShorthand::Catch)
.then(|| self.await_catch_keyword(block))
.flatten(),
catch_body: sections.catch.map(&body),
}
}
fn compose_await_tail(&self, p: &AwaitPieces, multiline: bool) -> DocId {
let d = self.d();
let mut parts: DocBuf = DocBuf::new();
if let Some(pending) = p.pending {
parts.push(self.indent_body_expand(pending, multiline));
}
if let Some(kw) = p.then_kw {
if multiline {
parts.push(d.hardline());
}
parts.push(kw);
}
if let Some(then_body) = p.then_body {
parts.push(self.indent_body_expand(then_body, multiline));
}
if let Some(kw) = p.catch_kw {
if multiline {
parts.push(d.hardline());
}
parts.push(kw);
}
if let Some(catch_body) = p.catch_body {
parts.push(self.indent_body_expand(catch_body, multiline));
}
if multiline && p.pending.is_none() && p.then_body.is_none() && p.catch_body.is_none() {
parts.push(self.indent_body_expand(d.empty(), true));
}
if multiline {
parts.push(d.hardline());
}
parts.push(d.text("{/await}"));
d.concat(&parts)
}
pub(super) fn build_await_block_doc_with_full_context(
&self,
block: &ast::AwaitBlock<'_>,
in_multiline_context: bool,
has_preceding_breakable: bool,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let ws = SectionWhitespace::Trimmed;
let sections = AwaitSections::classify(block, ws);
let allow_wrapping = !has_preceding_breakable;
let head = self.build_block_head_expr(
AWAIT_BLOCK_OPEN,
block.opening_tag_span,
block.expression,
await_expr_comment_end(block, sections.shorthand),
allow_wrapping || in_multiline_context,
HeadTail::of_await(sections.shorthand),
);
let can_wrap = self.block_head_can_wrap(allow_wrapping, in_multiline_context);
let has_section = sections.pending.is_some_and(|p| !ws.renders_nothing(p))
|| sections.then.is_some()
|| sections.catch.is_some_and(|c| !ws.renders_nothing(c));
let all_sections_inline = [&block.pending, &block.then, &block.catch]
.into_iter()
.filter_map(Option::as_ref)
.all(|f| self.fragment_inline_authored(f));
let clause = self.build_await_clause(block, sections.shorthand);
let clause_forced_break = clause.is_some_and(|c| d.will_break(c));
let head_doc = self.build_block_head(
AWAIT_BLOCK_OPEN,
block.expression,
head,
HeadCloser::BlockTag(clause),
can_wrap,
);
if has_section && all_sections_inline {
let pieces =
self.build_await_pieces(block, §ions, |f| self.build_section_body_doc(f));
let inline_tail = self.compose_await_tail(&pieces, false);
let multiline_tail = self.compose_await_tail(&pieces, true);
return self.build_expanding_construct(
head_doc,
clause_forced_break,
inline_tail,
multiline_tail,
gt_prefix,
);
}
let expand = !all_sections_inline;
let pieces = self.build_await_pieces(block, §ions, |f| {
if expand {
self.build_nodes_doc_multiline(f.nodes)
} else {
d.empty()
}
});
let tail = self.compose_await_tail(&pieces, expand);
let block_doc = if clause_forced_break {
d.concat(&[head_doc, tail, d.break_parent()])
} else {
d.concat(&[head_doc, tail])
};
self.dangle_gt(gt_prefix, block_doc)
}
pub(super) fn build_key_block_doc_with_full_context(
&self,
block: &ast::KeyBlock<'_>,
in_multiline_context: bool,
has_preceding_breakable: bool,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let allow_wrapping = !has_preceding_breakable;
let head = self.build_block_head_expr(
KEY_BLOCK_OPEN,
block.opening_tag_span,
block.expression,
block.opening_tag_span.end - 1,
allow_wrapping || in_multiline_context,
HeadTail::Delimiter,
);
let can_wrap = self.block_head_can_wrap(allow_wrapping, in_multiline_context);
let head_doc = self.build_block_head(
KEY_BLOCK_OPEN,
block.expression,
head,
HeadCloser::BlockTag(None),
can_wrap,
);
let close = d.text("{/key}");
if self.fragment_inline_authored(&block.fragment) {
let body_doc = self.build_section_body_doc(&block.fragment);
return self.build_expanding_block(head_doc, body_doc, close, gt_prefix);
}
let body_doc = self.build_nodes_doc_multiline(block.fragment.nodes);
let parts: DocBuf = smallvec![
head_doc,
self.indent_body_expand(body_doc, true),
d.hardline(),
close,
];
self.dangle_gt(gt_prefix, d.concat(&parts))
}
pub(super) fn build_snippet_opening_doc(&self, block: &ast::SnippetBlock<'_>) -> DocId {
let d = self.d();
let type_params_part = if let Some(decl) = &block.type_parameters {
tsv_ts::__private::build_type_parameters_doc(d, decl, &self.ts_inputs(), self.embed)
} else {
d.empty()
};
let params_inner = match block.params_paren {
Some(paren) => tsv_ts::__private::build_function_params_doc(
d,
block.parameters,
Some(paren.start),
Some(paren.end),
&self.ts_inputs(),
self.embed,
),
None => d.text("()"),
};
let params_doc = d.group(params_inner);
d.group(d.concat(&[
d.text("{#snippet "),
d.source_span(block.expression.span(), self.source),
type_params_part,
params_doc,
d.text("}"),
]))
}
pub(super) fn build_snippet_block_doc_with_full_context(
&self,
block: &ast::SnippetBlock<'_>,
gt_prefix: Option<DocId>,
) -> DocId {
let d = self.d();
let is_inline = self.fragment_inline_authored(&block.body);
let opening_doc = self.build_snippet_opening_doc(block);
if is_inline {
let body_doc = self.build_section_body_doc(&block.body);
let close = d.text("{/snippet}");
return self.build_expanding_block(opening_doc, body_doc, close, gt_prefix);
}
let body_doc = self.build_nodes_doc_multiline(block.body.nodes);
let parts: DocBuf = smallvec![
opening_doc,
self.indent_body_expand(body_doc, true),
d.hardline(),
d.text("{/snippet}"),
];
self.dangle_gt(gt_prefix, d.concat(&parts))
}
}