mod class;
mod control_flow;
pub(in crate::printer) mod function;
mod head_parens;
mod modules;
pub(in crate::printer) mod restricted_production;
mod type_declarations;
pub(in crate::printer) mod variable;
pub(super) use super::{Printer, build_entity_name_doc, is_effectively_empty_body};
use super::LeadingGlue;
use super::ParenContext;
use super::RunLeadingBlank;
use super::class_expr_has_decorators;
use super::expressions::literals::format_directive;
use super::expressions::operators::SeqLayout;
use super::needs_parens::LeftmostText;
use crate::ast::{self, Expression, ExpressionKind, Statement, StatementKind};
use smallvec::smallvec;
use tsv_lang::Span;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::DocId;
#[derive(Clone, Copy)]
pub(in crate::printer) struct StatementContext {
pub(in crate::printer) in_program_or_block: bool,
clause_tail_dedent: Option<u8>,
tail_reaches_list: bool,
}
#[derive(Clone, Copy, PartialEq, Eq)]
pub(in crate::printer) enum TerminatorGap {
ListClaims,
ClauseTail(u8),
}
impl TerminatorGap {
pub(in crate::printer) const fn clause_tail(self) -> Option<u8> {
match self {
Self::ClauseTail(dedent) => Some(dedent),
Self::ListClaims => None,
}
}
pub(in crate::printer) const fn list_claims(self) -> bool {
matches!(self, Self::ListClaims)
}
}
impl StatementContext {
pub(in crate::printer) const PROGRAM_OR_BLOCK: Self = Self {
in_program_or_block: true,
clause_tail_dedent: None,
tail_reaches_list: true,
};
pub(in crate::printer) const OTHER_LIST: Self = Self {
in_program_or_block: false,
clause_tail_dedent: None,
tail_reaches_list: true,
};
pub(in crate::printer) fn clause_body(self, continues: bool, indented: bool) -> Self {
let base = if continues {
0
} else {
self.clause_tail_dedent.unwrap_or(0)
};
Self {
in_program_or_block: false,
clause_tail_dedent: Some(base.saturating_add(u8::from(indented))),
tail_reaches_list: self.tail_reaches_list && !continues,
}
}
pub(in crate::printer) fn labeled_body(self) -> Self {
Self {
in_program_or_block: false,
..self
}
}
pub(in crate::printer) fn clause_tail(self) -> Option<u8> {
self.clause_tail_dedent
}
pub(in crate::printer) fn terminator_gap(self) -> TerminatorGap {
match self.clause_tail_dedent {
None => TerminatorGap::ListClaims,
Some(_) if self.tail_reaches_list => TerminatorGap::ListClaims,
Some(dedent) => TerminatorGap::ClauseTail(dedent),
}
}
}
impl<'a> Printer<'a> {
pub(super) fn build_statement_doc(
&self,
statement: &Statement<'_>,
ctx: StatementContext,
) -> DocId {
tsv_lang::fanout_probe::build(
tsv_lang::fanout_probe::Build::Statement,
&statement.kind,
|| (statement.span.start, statement.span.end),
);
let outer = self.enclosing_statement.replace(Some(statement.span));
let built =
self.with_line_ending_construct(|| self.build_statement_doc_uncounted(statement, ctx));
self.enclosing_statement.set(outer);
built
}
fn build_statement_doc_uncounted(
&self,
statement: &Statement<'_>,
ctx: StatementContext,
) -> DocId {
let d = self.d();
match &statement.kind {
StatementKind::ExpressionStatement(stmt) => {
self.build_expression_statement_doc(stmt, statement.span, ctx)
}
StatementKind::VariableDeclaration(decl) => {
self.build_variable_declaration_doc(decl, Some(ctx.terminator_gap()))
}
StatementKind::TSTypeAliasDeclaration(decl) => {
self.build_type_alias_declaration_doc(decl, statement.span, ctx.clause_tail())
}
StatementKind::ReturnStatement(ret) => {
self.build_return_statement_doc(ret, statement.span, ctx.terminator_gap())
}
StatementKind::BlockStatement(block) => {
self.build_block_statement_expand_empty_doc(block)
}
StatementKind::FunctionDeclaration(decl) => self.build_function_declaration_doc(decl),
StatementKind::ClassDeclaration(decl) => self.build_class_declaration_doc(decl),
StatementKind::ExportNamedDeclaration(decl) => {
self.build_export_named_declaration_doc(decl, statement.span)
}
StatementKind::ExportDefaultDeclaration(decl) => {
self.build_export_default_declaration_doc(decl, statement.span)
}
StatementKind::ExportAllDeclaration(decl) => {
self.build_export_all_declaration_doc(decl, statement.span)
}
StatementKind::TSExportAssignment(decl) => {
self.build_export_assignment_doc(decl, statement.span)
}
StatementKind::TSNamespaceExportDeclaration(decl) => {
self.build_namespace_export_declaration_doc(decl, statement.span)
}
StatementKind::ImportDeclaration(decl) => {
self.build_import_declaration_doc(decl, statement.span)
}
StatementKind::TSImportEqualsDeclaration(decl) => {
self.build_import_equals_declaration_doc(decl, statement.span)
}
StatementKind::IfStatement(stmt) => {
self.build_if_statement_doc(stmt, statement.span, ctx)
}
StatementKind::ForStatement(stmt) => {
self.build_for_statement_doc(stmt, statement.span, ctx)
}
StatementKind::ForInStatement(stmt) => {
self.build_for_in_statement_doc(stmt, statement.span, ctx)
}
StatementKind::ForOfStatement(stmt) => {
self.build_for_of_statement_doc(stmt, statement.span, ctx)
}
StatementKind::WhileStatement(stmt) => {
self.build_while_statement_doc(stmt, statement.span, ctx)
}
StatementKind::DoWhileStatement(stmt) => {
self.build_do_while_statement_doc(stmt, statement.span, ctx)
}
StatementKind::WithStatement(stmt) => {
self.build_with_statement_doc(stmt, statement.span, ctx)
}
StatementKind::SwitchStatement(stmt) => {
self.build_switch_statement_doc(stmt, statement.span)
}
StatementKind::TryStatement(stmt) => self.build_try_statement_doc(stmt, statement.span),
StatementKind::ThrowStatement(stmt) => {
self.build_throw_statement_doc(stmt, statement.span, ctx.terminator_gap())
}
StatementKind::BreakStatement(stmt) => {
self.build_break_statement_doc(stmt, statement.span, ctx.terminator_gap())
}
StatementKind::ContinueStatement(stmt) => {
self.build_continue_statement_doc(stmt, statement.span, ctx.terminator_gap())
}
StatementKind::LabeledStatement(stmt) => self.build_labeled_statement_doc(stmt, ctx),
StatementKind::EmptyStatement(_) => d.text(";"),
StatementKind::DebuggerStatement(_) => self.build_bare_keyword_terminator_doc(
"debugger",
statement.span,
ctx.terminator_gap(),
),
StatementKind::TSInterfaceDeclaration(decl) => {
self.build_interface_declaration_doc(decl)
}
StatementKind::TSDeclareFunction(decl) => {
self.build_declare_function_doc(decl, ctx.clause_tail())
}
StatementKind::TSEnumDeclaration(decl) => {
self.build_enum_declaration_doc(decl, statement.span)
}
StatementKind::TSModuleDeclaration(decl) => {
self.build_module_declaration_doc(decl, ctx.clause_tail())
}
}
}
fn build_expression_statement_doc(
&self,
stmt: &ast::ExpressionStatement<'_>,
span: Span,
ctx: StatementContext,
) -> DocId {
let d = self.d();
let expr_end = stmt.expression.span().end;
let (value_doc, gap_start) = if stmt.is_directive {
(self.build_directive_doc(stmt), expr_end)
} else {
let mut gap_start = expr_end;
let doc = self.build_value_with_outermost_owned_comment(stmt.expression, || {
let (doc, start) =
self.build_expression_statement_value_doc(stmt, span, ctx.in_program_or_block);
gap_start = start;
doc
});
(doc, gap_start)
};
if self.semicolon_gap_is_bare(gap_start, span.end) {
return d.concat(&[value_doc, d.text(";")]);
}
let mut parts: DocBuf = smallvec![value_doc];
if gap_start < expr_end
&& ctx.clause_tail().is_some()
&& self.push_handed_clause_semicolon(
&mut parts,
Span::new(gap_start, expr_end),
span.end,
ctx.terminator_gap(),
)
{
return d.concat(&parts);
}
self.push_statement_semicolon(&mut parts, gap_start, span.end, ctx.terminator_gap());
d.concat(&parts)
}
fn push_handed_clause_semicolon(
&self,
parts: &mut DocBuf,
handed: Span,
span_end: u32,
gap: TerminatorGap,
) -> bool {
let claim_end = self.node_terminator_claim_end(handed.start, span_end, gap);
if self.has_comments_to_emit_between(handed.end, claim_end) {
return false;
}
let bytes_len = self.source.len() as u32;
let next = self
.next_significant_byte(span_end, bytes_len)
.map_or(bytes_len, |pos| pos as u32);
if self.comments_to_emit_between(claim_end, next).any(|c| {
c.is_block
&& (c.span.start < span_end || !self.has_newline_between(span_end, c.span.start))
}) {
return false;
}
parts.push(self.d().text(";"));
for comment in self.comments_to_emit_between(handed.start, handed.end) {
parts.push(self.build_trailing_line_comment_doc(comment));
}
true
}
fn build_directive_doc(&self, stmt: &ast::ExpressionStatement<'_>) -> DocId {
let d = self.d();
let span = stmt.expression.span();
let text = d.text_pooled(&format_directive(span.extract(self.source)));
self.prepend_owned_leading_comment_at(span.start, text)
}
#[inline]
fn build_expression_statement_expr_doc(
&self,
stmt: &ast::ExpressionStatement<'_>,
nested_paren: Option<Span>,
) -> DocId {
self.expr_stmt_paren_target.set(nested_paren);
self.is_expression_statement.set(true);
let doc = match &stmt.expression {
Expression {
span,
kind: ExpressionKind::SequenceExpression(seq),
} => self.build_sequence_doc(seq, *span, SeqLayout::Indented),
expr => self.build_expression_doc(expr),
};
self.is_expression_statement.set(false);
self.expr_stmt_paren_target.set(None);
doc
}
fn build_expression_statement_value_doc(
&self,
stmt: &ast::ExpressionStatement<'_>,
span: Span,
in_program_or_block: bool,
) -> (DocId, u32) {
let d = self.d();
let expr_end = stmt.expression.span().end;
let shell_close = self.value_paren_line_comment_close(expr_end, span.end);
let expr_start = stmt.expression.span().start;
let source_paren = self.statement_opens_with_paren(span, expr_start);
let frozen = source_paren
.then(|| self.value_head_frozen_span(span.start + 1, stmt.expression.span()))
.flatten();
let mut needs_parens = self
.needs_parens(stmt.expression, ParenContext::ExpressionStatement)
|| self.needs_avoid_directive_parens(stmt, in_program_or_block);
let nested_paren = if needs_parens {
None
} else {
let text = LeftmostText::of_value(frozen.is_some());
self.expr_stmt_nested_paren_target(stmt.expression, text)
};
needs_parens |= frozen.is_some() && nested_paren.is_some();
let paren_gap = || self.comments_to_emit_between(span.start + 1, expr_start);
let paren_open_comments = needs_parens
&& source_paren
&& self.has_comments_to_emit_between(span.start + 1, expr_start);
let plain = !needs_parens && !source_paren && shell_close.is_none();
let mut tail_gap_start = expr_end;
let expr_doc = match frozen {
Some(span) => self.build_frozen_expression_doc(stmt.expression, span),
None => {
let (doc, gap_start) = self
.with_statement_tail(plain.then_some(stmt.expression), || {
self.build_expression_statement_expr_doc(stmt, nested_paren)
});
tail_gap_start = gap_start.unwrap_or(expr_end);
doc
}
};
let decorated_class_expr = needs_parens
&& matches!(
&stmt.expression.kind,
ExpressionKind::ClassExpression(c) if class_expr_has_decorators(c)
);
if !needs_parens && !source_paren && shell_close.is_none() {
return (expr_doc, tail_gap_start);
}
let mut parts = DocBuf::new();
let mut consumed_close = None;
if paren_open_comments {
let mut inner: DocBuf = smallvec![d.hardline()];
self.push_leading_comment_run(
&mut inner,
paren_gap(),
expr_start,
LeadingGlue::Adjacent,
);
inner.push(expr_doc);
consumed_close = self.push_retained_gap_trailing_run(&mut inner, expr_end, shell_close);
parts.push(d.text("("));
parts.push(d.indent(d.concat(&inner)));
parts.push(d.hardline());
parts.push(d.text(")"));
} else if decorated_class_expr {
parts.push(self.build_break_open_parens(expr_doc));
} else if let Some(close) = shell_close {
let mut inner = DocBuf::new();
if !needs_parens && source_paren {
self.push_leading_comment_run(
&mut inner,
paren_gap(),
expr_start,
LeadingGlue::Adjacent,
);
}
inner.push(expr_doc);
consumed_close = self.push_retained_gap_trailing_run(&mut inner, expr_end, Some(close));
parts.push(d.text("("));
parts.push(d.indent_hardline(d.concat(&inner)));
parts.push(d.hardline());
parts.push(d.text(")"));
} else {
if needs_parens {
parts.push(d.text("("));
} else if source_paren {
self.push_leading_comment_run(
&mut parts,
paren_gap(),
expr_start,
LeadingGlue::Adjacent,
);
}
parts.push(expr_doc);
if needs_parens {
parts.push(d.text(")"));
}
}
let gap_start =
consumed_close.map_or(expr_end, |close| Self::past_grouping_close(close, span.end));
(d.concat(&parts), gap_start)
}
fn push_retained_gap_trailing_run(
&self,
inner: &mut DocBuf,
expr_end: u32,
close: Option<u32>,
) -> Option<u32> {
let close = close?;
self.push_anchored_trailing_run(inner, expr_end, close, RunLeadingBlank::Keep);
Some(close)
}
fn build_return_statement_doc(
&self,
ret: &ast::ReturnStatement<'_>,
span: Span,
gap: TerminatorGap,
) -> DocId {
let Some(arg) = &ret.argument else {
return self.build_bare_keyword_terminator_doc("return", span, gap);
};
self.build_keyword_argument_doc("return", span.start, span.end, arg, gap)
}
pub(in crate::printer::statements) fn build_bare_keyword_terminator_doc(
&self,
keyword: &'static str,
span: Span,
gap: TerminatorGap,
) -> DocId {
let d = self.d();
let keyword_end = span.start + keyword.len() as u32;
let mut parts: DocBuf = smallvec![d.text(keyword)];
self.push_statement_semicolon(&mut parts, keyword_end, span.end, gap);
d.concat(&parts)
}
}