use super::analysis::{child_stops_optional_chain, member_lookup_node};
use super::printing::{member_lookup_group, print_node};
use super::types::{ChainGroup, ChainNode};
use crate::ast::{Expression, ExpressionKind, MemberExpression};
use crate::printer::Printer;
use std::cell::Cell;
use tsv_lang::Span;
use tsv_lang::doc::arena::DocId;
#[derive(Debug, Clone, Copy, Default)]
pub(super) struct InlineLookups {
pub every: bool,
pub call_tail: bool,
}
impl InlineLookups {
pub(super) const NONE: Self = Self {
every: false,
call_tail: false,
};
}
pub(super) fn resolve_inline_lookups(
chain_span: Span,
groups: &[ChainGroup<'_>],
printer: &Printer<'_>,
) -> InlineLookups {
let marked = |mark: &Cell<Option<Span>>| mark.get() == Some(chain_span);
InlineLookups {
every: marked(&printer.inline_every_member_lookup)
&& !groups
.iter()
.any(|g| g.nodes.iter().any(ChainNode::is_optional_link)),
call_tail: marked(&printer.inline_member_call_tail),
}
}
pub(crate) fn build_lookup_after_operand_doc(
member: &MemberExpression<'_>,
span: Span,
object_doc: DocId,
printer: &Printer<'_>,
) -> DocId {
let node = member_lookup_node(member, span, printer.linearize_input());
let lookup = print_node(&node, printer);
let marked = |mark: &Cell<Option<Span>>| mark.get() == Some(span);
let inline = !node.is_dot_lookup()
|| (marked(&printer.inline_every_member_lookup) && !spine_has_optional_link(member, span))
|| (marked(&printer.inline_member_call_tail)
&& matches!(
strip_non_null(member.object).kind,
ExpressionKind::CallExpression(_)
));
let d = printer.arena();
let lookup = if inline {
lookup
} else {
member_lookup_group(d, lookup)
};
d.concat(&[object_doc, lookup])
}
fn strip_non_null<'e, 'a>(mut expr: &'e Expression<'a>) -> &'e Expression<'a> {
while let ExpressionKind::TSNonNullExpression(non_null) = &expr.kind {
expr = non_null.expression;
}
expr
}
fn spine_has_optional_link(member: &MemberExpression<'_>, span: Span) -> bool {
if member.optional {
return true;
}
let (mut parent_start, mut expr) = (span.start, member.object);
loop {
if child_stops_optional_chain(parent_start, false, expr) {
return false;
}
let next = match &expr.kind {
ExpressionKind::MemberExpression(m) if m.optional => return true,
ExpressionKind::MemberExpression(m) => m.object,
ExpressionKind::CallExpression(c) if c.optional => return true,
ExpressionKind::CallExpression(c) => c.callee,
ExpressionKind::TSNonNullExpression(n) => n.expression,
_ => return false,
};
parent_start = expr.span.start;
expr = next;
}
}
impl<'a> Printer<'a> {
pub(in crate::printer) fn mark_assignment_target_member_lookups(&self, left: &Expression<'_>) {
self.mark_inline_every_member_lookup(match &left.kind {
ExpressionKind::Identifier(_) => None,
_ => Some(left.span()),
});
}
pub(in crate::printer) fn mark_new_callee_member_lookups(&self, callee: &Expression<'_>) {
self.mark_inline_every_member_lookup(Some(callee.span()));
}
pub(in crate::printer) fn mark_member_call_tail_operand(&self, operand: &Expression<'_>) {
self.inline_member_call_tail.set(Some(operand.span()));
}
pub(in crate::printer) fn build_with_inline_every_lookup_forwarded(
&self,
span: Span,
operand: &Expression<'_>,
build: impl FnOnce() -> DocId,
) -> DocId {
if self.inline_every_member_lookup.get() != Some(span) {
return build();
}
self.inline_every_member_lookup.set(Some(operand.span()));
let doc = build();
self.inline_every_member_lookup.set(Some(span));
doc
}
pub(in crate::printer) fn build_with_inline_member_call_tail_forwarded<T>(
&self,
span: Span,
operand: &Expression<'_>,
build: impl FnOnce() -> T,
) -> T {
if self.inline_member_call_tail.get() != Some(span) {
return build();
}
self.inline_member_call_tail.set(Some(operand.span()));
let built = build();
self.inline_member_call_tail.set(Some(span));
built
}
fn mark_inline_every_member_lookup(&self, chain: Option<Span>) {
self.inline_every_member_lookup.set(chain);
}
}