use super::super::Printer;
use super::super::comments::ParenLeadingValue;
use super::arg_comments::{PartitionedComments, should_force_expansion_for_comments};
use super::arg_predicates::{is_expandable_object, is_hook_callback_with_deps};
use super::arg_wrapping::try_hug_multiline_template_arg;
use crate::ast;
use smallvec::smallvec;
use tsv_lang::Span;
use tsv_lang::doc::DocBuf;
use tsv_lang::doc::arena::{DocArena, DocId};
fn import_phase_word(phase: ast::ImportPhase) -> Option<&'static str> {
match phase {
ast::ImportPhase::None => None,
ast::ImportPhase::Source => Some("source"),
ast::ImportPhase::Defer => Some("defer"),
}
}
fn build_import_head_doc(
printer: &Printer<'_>,
import_expr: &ast::ImportExpression<'_>,
import_expr_span: Span,
) -> (DocId, u32) {
let d = printer.d();
let start = import_expr_span.start;
let import_end = start + "import".len() as u32;
let source_start = import_expr.source.span().start;
let (head, head_end) = match import_phase_word(import_expr.phase) {
None => (d.text("import"), import_end),
Some(word) => match printer.find_keyword_in_range(import_end, source_start, word) {
Some(word_start) => (
printer.build_dotted_pair_doc(
d.text("import"),
d.text(word),
import_end,
word_start,
),
word_start + word.len() as u32,
),
None => {
debug_assert!(
false,
"phased import has no `{word}` in source[{import_end}..{source_start}]"
);
(d.text("import"), import_end)
}
},
};
(head, head_end)
}
fn wrap_import_args(
d: &DocArena,
open: DocId,
paren_line: &[DocId],
inner: DocId,
force_break: bool,
) -> DocId {
if force_break || !paren_line.is_empty() {
d.concat(&[
open,
d.concat(paren_line),
d.indent_hardline(inner),
d.hardline(),
d.text(")"),
])
} else {
d.group(d.concat(&[open, d.indent_softline(inner), d.softline(), d.text(")")]))
}
}
#[derive(Clone, Copy)]
pub(in crate::printer) struct ImportOptionsArg {
pub doc: DocId,
pub start: u32,
pub end: u32,
}
pub(in crate::printer) fn build_import_args_comment_layout(
printer: &Printer<'_>,
open: DocId,
source: &ParenLeadingValue,
source_end: u32,
options: Option<ImportOptionsArg>,
paren_close: u32,
) -> Option<DocId> {
let d = printer.d();
let (paren_line, source_doc, leading_forces_break) = (
source.paren_line.as_slice(),
source.value,
source.forces_break,
);
let Some(options) = options else {
if printer.has_comments_to_emit_between(source_end, paren_close) {
let pc = PartitionedComments::for_closer_gap(printer, source_end, paren_close);
let mut parts = smallvec![source_doc];
pc.emit_last_arg_comments(&mut parts, printer);
let inner = d.concat(&parts);
return Some(wrap_import_args(
d,
open,
paren_line,
inner,
pc.forces_closer_break() || leading_forces_break,
));
}
return leading_forces_break
.then(|| wrap_import_args(d, open, paren_line, source_doc, true));
};
let has_inter_comments = printer.has_comments_on_page_between(source_end, options.start);
let has_trailing_comments = printer.has_comments_to_emit_between(options.end, paren_close);
let inter_blank_no_comments =
!has_inter_comments && printer.is_next_line_empty(source_end, options.start);
if !(leading_forces_break
|| has_inter_comments
|| has_trailing_comments
|| inter_blank_no_comments)
{
return None;
}
let inter = PartitionedComments::for_routed_arg_gap(printer, source_end, options.start);
let mut head = smallvec![source_doc];
inter.emit_trailing_comments_around_comma(&mut head, printer);
let inter_blank =
inter_blank_no_comments || (has_inter_comments && inter.has_blank_line_in_gap(printer));
let mut tail = DocBuf::new();
inter.emit_leading_comments_inline_aware(&mut tail, printer);
tail.push(options.doc);
let trailing = PartitionedComments::for_closer_gap(printer, options.end, paren_close);
trailing.emit_last_arg_comments(&mut tail, printer);
let force_break = leading_forces_break
|| inter_blank
|| trailing.forces_closer_break()
|| should_force_expansion_for_comments(printer, source_end, options.start);
let sep = if inter_blank {
d.concat(&[d.literalline(), d.hardline()])
} else if force_break {
d.hardline()
} else {
d.line()
};
let body = d.concat(&[d.concat(&head), sep, d.concat(&tail)]);
Some(wrap_import_args(d, open, paren_line, body, force_break))
}
pub(super) fn build_import_expression_doc(
printer: &Printer<'_>,
import_expr: &ast::ImportExpression<'_>,
span: Span,
) -> DocId {
let d = printer.d();
let (head, leading_scan_start) = build_import_head_doc(printer, import_expr, span);
if let Some((split_start, paren)) = super::paren_split_for_outlined(
printer,
leading_scan_start,
std::slice::from_ref(&import_expr.source),
) {
let args = build_import_args_doc(printer, import_expr, span, d.empty(), paren);
return super::hang_args_under_split(printer, head, (split_start, paren), args);
}
build_import_args_doc(printer, import_expr, span, head, leading_scan_start)
}
fn build_import_args_doc(
printer: &Printer<'_>,
import_expr: &ast::ImportExpression<'_>,
span: Span,
head: DocId,
leading_scan_start: u32,
) -> DocId {
let d = printer.d();
let source_start = import_expr.source.span().start;
if import_expr.options.is_none()
&& let Some(doc) = try_hug_multiline_template_arg(
printer,
head,
std::slice::from_ref(&import_expr.source),
leading_scan_start,
span.end,
)
{
return doc;
}
let open = d.concat(&[head, d.text("(")]);
printer.mark_relational_sibling_authored_pairs(
[Some(import_expr.source), import_expr.options].into_iter(),
);
let frozen_source = printer.gap_frozen_span(leading_scan_start, import_expr.source.span());
let raw_source_doc = frozen_source.map_or_else(
|| {
printer.wrap_relational_sibling_pair(
import_expr.source,
printer.for_init_in_needs_parens(import_expr.source),
printer.wrap_for_init_in(
import_expr.source,
printer.build_expression_doc_claiming_outermost(import_expr.source),
),
)
},
|frozen| printer.build_frozen_arg_doc(import_expr.source, frozen),
);
let leading =
printer.build_paren_leading_value_doc(leading_scan_start, source_start, raw_source_doc);
let source_doc = leading.value;
let source_end = import_expr.source.span().end;
let paren_close = span.end;
let options_arg = import_expr
.options
.as_ref()
.map(|options| ImportOptionsArg {
doc: printer
.gap_frozen_span(source_end, options.span())
.map_or_else(
|| printer.wrap_for_init_in(options, printer.build_expression_doc(options)),
|frozen| printer.build_frozen_arg_doc(options, frozen),
),
start: options.span().start,
end: options.span().end,
});
if let Some(doc) = build_import_args_comment_layout(
printer,
open,
&leading,
source_end,
options_arg,
paren_close,
) {
return doc;
}
let Some((options, options_doc)) = import_expr
.options
.as_ref()
.zip(options_arg.map(|options| options.doc))
else {
return wrap_import_args(d, open, &[], source_doc, false);
};
if is_hook_callback_with_deps(import_expr.source, options)
&& !printer.has_comments_on_page_between(leading_scan_start, source_start)
{
return d.concat(&[open, source_doc, d.text(", "), options_doc, d.text(")")]);
}
if is_expandable_object(options) {
let state_flat = d.concat(&[open, source_doc, d.text(", "), options_doc, d.text(")")]);
let expanded_options = d.group_break(options_doc);
let state_expand_last = d.concat(&[
open,
source_doc,
d.text(", "),
expanded_options,
d.text(")"),
]);
let arg_parts = d.join_doc([source_doc, options_doc], d.comma_line());
let state_expand_all = d.concat(&[
open,
d.indent_softline(arg_parts),
d.softline(),
d.text(")"),
]);
d.conditional_group(&[state_flat, state_expand_last, state_expand_all])
} else {
let arg_parts = d.join_doc([source_doc, options_doc], d.comma_line());
wrap_import_args(d, open, &[], arg_parts, false)
}
}
pub(super) fn build_meta_property_doc(
printer: &Printer<'_>,
meta: &ast::MetaProperty<'_>,
) -> DocId {
printer.build_dotted_pair_doc(
printer.identifier_name_doc(&meta.meta),
printer.identifier_name_doc(&meta.property),
meta.meta.span.end,
meta.property.span.start,
)
}