use super::declarations::write_class_expression;
use super::functions::{
call_callee_flags, write_arrow_function_expression, write_await_expression,
write_call_expression_tail, write_conditional_expression, write_function_expression,
write_member_expression_tail, write_new_expression, write_yield_expression,
};
use super::patterns::{
write_assignment_pattern, write_object_pattern, write_property, write_rest_element,
write_template_literal,
};
use super::types::{write_type, write_type_parameter_instantiation};
use super::{
CommentAttach, CommentMode, Ctx, JsonWriter, close_node, node_header, write_array,
write_bare_node, write_identifier_parts, write_identifier_plain, write_literal,
write_name_field, write_type_annotation_field, write_type_arguments_field,
};
use crate::ast;
use smallvec::SmallVec;
use tsv_lang::Span;
#[derive(Clone, Copy, Default, PartialEq, Eq)]
pub(super) enum ChainState {
#[default]
Unresolved,
InChain,
KnownFree,
}
#[derive(Clone, Copy, Default)]
pub(super) struct ExprFlags {
pub(super) chain: ChainState,
pub(super) force_optional: bool,
pub(super) strip_optional: bool,
}
pub(super) fn write_expression(w: &mut JsonWriter, expr: &ast::Expression<'_>, ctx: &Ctx<'_>) {
write_expression_inner(w, expr, ctx, ExprFlags::default());
}
pub(super) fn write_expressions<'a, 'arena: 'a>(
w: &mut JsonWriter,
items: impl IntoIterator<Item = &'a ast::Expression<'arena>>,
ctx: &Ctx<'_>,
) {
write_array(w, items, |w, e| write_expression(w, e, ctx));
}
fn write_expression_holes<'a, 'arena: 'a>(
w: &mut JsonWriter,
items: impl IntoIterator<Item = &'a Option<ast::Expression<'arena>>>,
ctx: &Ctx<'_>,
) {
write_array(w, items, |w, e| match e {
Some(expr) => write_expression(w, expr, ctx),
None => w.null(),
});
}
#[inline(never)]
pub(super) fn write_expression_inner(
w: &mut JsonWriter,
expr: &ast::Expression<'_>,
ctx: &Ctx<'_>,
flags: ExprFlags,
) {
match &expr.kind {
ast::ExpressionKind::Literal(lit) => write_literal(w, lit, ctx),
ast::ExpressionKind::Identifier(id)
if id.extra.is_none() && !id.optional && !flags.force_optional =>
{
write_identifier_plain(w, id, ctx);
}
ast::ExpressionKind::Identifier(id) => {
write_expression_identifier(w, id, flags.force_optional, ctx);
}
_ => write_expression_full_dispatch(w, expr, ctx, flags),
}
}
#[cold]
#[inline(never)]
fn write_comment_bound_pair_paren_frame(
w: &mut JsonWriter,
span: Span,
inner: &ast::Expression<'_>,
ctx: &Ctx<'_>,
flags: ExprFlags,
attach: &CommentAttach<'_>,
) {
attach.paren_open(span);
write_expression_inner(w, inner, ctx, flags);
attach.paren_close(span);
}
#[inline(never)]
fn write_expression_identifier(
w: &mut JsonWriter,
id: &ast::Identifier<'_>,
force_optional: bool,
ctx: &Ctx<'_>,
) {
write_identifier_parts(
w,
id.span,
id.ident_name(),
id.optional || force_optional,
id.type_annotation(),
id.decorators(),
ctx,
);
}
#[inline(never)]
fn write_binary_chain(
w: &mut JsonWriter,
binary: &ast::BinaryExpression<'_>,
span: Span,
ctx: &Ctx<'_>,
) {
fn node_type(operator: ast::BinaryOperator) -> &'static str {
match operator {
ast::BinaryOperator::AmpersandAmpersand
| ast::BinaryOperator::PipePipe
| ast::BinaryOperator::QuestionQuestion => "LogicalExpression",
_ => "BinaryExpression",
}
}
let mut spine: SmallVec<[(&ast::BinaryExpression<'_>, Span); 8]> = SmallVec::new();
let (mut node, mut node_span) = (binary, span);
loop {
node_header(w, node_type(node.operator), node_span, ctx);
w.raw(",\"left\":");
spine.push((node, node_span));
match &node.left.kind {
ast::ExpressionKind::BinaryExpression(left) => {
node_span = node.left.span;
node = left;
}
_ => {
write_expression(w, node.left, ctx);
break;
}
}
}
for &(node, node_span) in spine.iter().rev() {
w.raw(",\"operator\":\"");
w.raw(node.operator.as_str());
w.raw("\",\"right\":");
write_expression(w, node.right, ctx);
close_node(w, node_type(node.operator), node_span, ctx);
}
}
#[inline(never)]
fn write_expression_full_dispatch(
w: &mut JsonWriter,
expr: &ast::Expression<'_>,
ctx: &Ctx<'_>,
flags: ExprFlags,
) {
match &expr.kind {
ast::ExpressionKind::CommentBoundPair(cast) => {
let flags = ExprFlags {
chain: ChainState::Unresolved,
..flags
};
if let CommentMode::Attach(attach) = ctx.comments
&& attach.removes_parens()
{
write_comment_bound_pair_paren_frame(w, expr.span, cast.inner, ctx, flags, attach);
} else {
write_expression_inner(w, cast.inner, ctx, flags);
}
}
ast::ExpressionKind::ParenthesizedExpression(paren) => {
node_header(w, "ParenthesizedExpression", expr.span, ctx);
w.raw(",\"expression\":");
write_expression(w, paren.expression, ctx);
close_node(w, "ParenthesizedExpression", expr.span, ctx);
}
ast::ExpressionKind::Literal(lit) => write_literal(w, lit, ctx),
ast::ExpressionKind::Identifier(id) => {
write_expression_identifier(w, id, flags.force_optional, ctx);
}
ast::ExpressionKind::PrivateIdentifier(pid) => {
node_header(w, "PrivateIdentifier", expr.span, ctx);
write_name_field(w, pid.name, expr.span.end - pid.name.raw_len as u32, ctx);
close_node(w, "PrivateIdentifier", expr.span, ctx);
}
ast::ExpressionKind::ObjectExpression(obj) => {
node_header(w, "ObjectExpression", expr.span, ctx);
w.raw(",\"properties\":");
super::write_body_array(w, obj.properties, ctx, |w, p| {
write_object_property(w, p, ctx);
});
close_node(w, "ObjectExpression", expr.span, ctx);
}
ast::ExpressionKind::ArrayExpression(arr) => {
node_header(w, "ArrayExpression", expr.span, ctx);
w.raw(",\"elements\":");
super::write_body_array_holes(w, arr.elements, ctx, |w, e| {
write_expression(w, e, ctx);
});
close_node(w, "ArrayExpression", expr.span, ctx);
}
ast::ExpressionKind::UnaryExpression(unary) => {
node_header(w, "UnaryExpression", unary.span, ctx);
w.raw(",\"operator\":\"");
w.raw(unary.operator.as_str());
w.raw_fixed(b"\",\"prefix\":true,\"argument\":");
write_expression(w, unary.argument, ctx);
close_node(w, "UnaryExpression", unary.span, ctx);
}
ast::ExpressionKind::UpdateExpression(update) => {
node_header(w, "UpdateExpression", expr.span, ctx);
w.raw(",\"operator\":\"");
w.raw(update.operator.as_str());
w.raw_pick(
update.prefix,
b"\",\"prefix\":true,\"argument\":",
b"\",\"prefix\":false,\"argument\":",
);
write_expression(w, update.argument, ctx);
close_node(w, "UpdateExpression", expr.span, ctx);
}
ast::ExpressionKind::BinaryExpression(binary) => {
write_binary_chain(w, binary, expr.span, ctx);
}
ast::ExpressionKind::ArrowFunctionExpression(arrow) => {
write_arrow_function_expression(w, arrow, expr.span, ctx);
}
ast::ExpressionKind::FunctionExpression(func) => {
write_function_expression(w, func, ctx, false);
}
ast::ExpressionKind::ClassExpression(class_expr) => {
write_class_expression(w, class_expr, expr.span, ctx);
}
ast::ExpressionKind::SpreadElement(spread) => {
node_header(w, "SpreadElement", spread.span, ctx);
w.raw(",\"argument\":");
write_expression(w, spread.argument, ctx);
close_node(w, "SpreadElement", spread.span, ctx);
}
ast::ExpressionKind::CallExpression(_)
| ast::ExpressionKind::MemberExpression(_)
| ast::ExpressionKind::TSNonNullExpression(_) => write_chain(w, expr, ctx, flags),
ast::ExpressionKind::NewExpression(new_expr) => {
write_new_expression(w, new_expr, expr.span, ctx);
}
ast::ExpressionKind::ConditionalExpression(cond) => {
write_conditional_expression(w, cond, expr.span, ctx);
}
ast::ExpressionKind::TemplateLiteral(template) => {
write_template_literal(w, template, ctx);
}
ast::ExpressionKind::TaggedTemplateExpression(tagged) => {
node_header(w, "TaggedTemplateExpression", expr.span, ctx);
w.raw(",\"tag\":");
write_expression(w, tagged.tag, ctx);
w.raw(",\"quasi\":");
write_template_literal(w, &tagged.quasi, ctx);
write_type_arguments_field(w, tagged.type_arguments.as_ref(), ctx);
close_node(w, "TaggedTemplateExpression", expr.span, ctx);
}
ast::ExpressionKind::AwaitExpression(await_expr) => {
write_await_expression(w, await_expr, expr.span, ctx);
}
ast::ExpressionKind::YieldExpression(yield_expr) => {
write_yield_expression(w, yield_expr, expr.span, ctx);
}
ast::ExpressionKind::SequenceExpression(seq) => {
node_header(w, "SequenceExpression", expr.span, ctx);
w.raw(",\"expressions\":");
write_expressions(w, seq.expressions.iter().copied(), ctx);
close_node(w, "SequenceExpression", expr.span, ctx);
}
ast::ExpressionKind::RegexLiteral(regex) => {
node_header(w, "Literal", expr.span, ctx);
w.raw(",\"value\":{},\"raw\":");
w.string(expr.span.extract(ctx.source));
w.raw(",\"regex\":{\"pattern\":");
w.string(regex.pattern(ctx.source));
w.raw(",\"flags\":");
w.string(regex.flags(ctx.source));
w.raw("}");
close_node(w, "Literal", expr.span, ctx);
}
ast::ExpressionKind::ThisExpression(_) => {
write_bare_node(w, "ThisExpression", expr.span, ctx);
}
ast::ExpressionKind::Super(_) => {
write_bare_node(w, "Super", expr.span, ctx);
}
ast::ExpressionKind::AssignmentExpression(assign) => {
node_header(w, "AssignmentExpression", expr.span, ctx);
w.raw(",\"operator\":\"");
w.raw(assign.operator.as_str());
w.raw("\",\"left\":");
write_expression(w, assign.left, ctx);
w.raw(",\"right\":");
write_expression(w, assign.right, ctx);
close_node(w, "AssignmentExpression", expr.span, ctx);
}
ast::ExpressionKind::ObjectPattern(obj) => {
write_object_pattern(w, obj, expr.span, ctx);
}
ast::ExpressionKind::ArrayPattern(arr) => {
super::patterns::pattern_header(
w,
"ArrayPattern",
expr.span,
arr.type_annotation.as_ref(),
ctx,
);
w.raw(",\"elements\":");
write_expression_holes(w, arr.elements, ctx);
if arr.optional {
w.raw(",\"optional\":true");
}
write_type_annotation_field(w, arr.type_annotation.as_ref(), ctx);
super::declarations::write_decorators_field(w, arr.decorators, ctx);
close_node(w, "ArrayPattern", expr.span, ctx);
}
ast::ExpressionKind::AssignmentPattern(pattern) => {
write_assignment_pattern(w, pattern, ctx, expr.span);
}
ast::ExpressionKind::RestElement(rest) => {
write_rest_element(w, rest, ctx);
}
ast::ExpressionKind::TSTypeAssertion(type_assert) => {
node_header(w, "TSTypeAssertion", expr.span, ctx);
w.raw(",\"typeAnnotation\":");
write_type(w, type_assert.type_annotation, ctx);
w.raw(",\"expression\":");
write_expression(w, type_assert.expression, ctx);
close_node(w, "TSTypeAssertion", expr.span, ctx);
}
ast::ExpressionKind::TSAsExpression(as_expr) => {
node_header(w, "TSAsExpression", expr.span, ctx);
w.raw(",\"expression\":");
write_expression(w, as_expr.expression, ctx);
w.raw(",\"typeAnnotation\":");
write_type(w, as_expr.type_annotation, ctx);
close_node(w, "TSAsExpression", expr.span, ctx);
}
ast::ExpressionKind::TSSatisfiesExpression(sat_expr) => {
node_header(w, "TSSatisfiesExpression", expr.span, ctx);
w.raw(",\"expression\":");
write_expression(w, sat_expr.expression, ctx);
w.raw(",\"typeAnnotation\":");
write_type(w, sat_expr.type_annotation, ctx);
close_node(w, "TSSatisfiesExpression", expr.span, ctx);
}
ast::ExpressionKind::TSInstantiationExpression(inst_expr) => {
node_header(w, "TSInstantiationExpression", expr.span, ctx);
w.raw(",\"expression\":");
write_expression_inner(
w,
inst_expr.expression,
ctx,
ExprFlags {
chain: ChainState::Unresolved,
force_optional: false,
strip_optional: flags.strip_optional,
},
);
w.raw(",\"typeArguments\":");
write_type_parameter_instantiation(w, &inst_expr.type_arguments, ctx);
close_node(w, "TSInstantiationExpression", expr.span, ctx);
}
ast::ExpressionKind::ImportExpression(import_expr) => {
node_header(w, "ImportExpression", expr.span, ctx);
w.raw(",\"source\":");
write_expression(w, import_expr.source, ctx);
if let Some(phase) = import_expr.phase.as_str() {
w.raw(",\"phase\":");
w.token(phase);
}
if ctx.vanilla_acorn {
w.raw(",\"options\":");
match &import_expr.options {
Some(opts) => write_expression(w, opts, ctx),
None => w.raw("null"),
}
} else if let Some(opts) = &import_expr.options {
w.raw(",\"arguments\":[");
write_expression(w, opts, ctx);
w.raw("]");
}
close_node(w, "ImportExpression", expr.span, ctx);
}
ast::ExpressionKind::MetaProperty(meta) => {
node_header(w, "MetaProperty", expr.span, ctx);
w.raw(",\"meta\":");
write_identifier_plain(w, &meta.meta, ctx);
w.raw(",\"property\":");
write_identifier_plain(w, &meta.property, ctx);
close_node(w, "MetaProperty", expr.span, ctx);
}
ast::ExpressionKind::TSParameterProperty(param_prop) => {
let param = param_prop.parameter.unwrap_comment_bound_pairs();
let override_ap = if let ast::ExpressionKind::AssignmentPattern(ap) = ¶m.kind {
let has_type_ann = match &ap.left.unwrap_comment_bound_pairs().kind {
ast::ExpressionKind::Identifier(id) => id.type_annotation().is_some(),
ast::ExpressionKind::ArrayPattern(arr) => arr.type_annotation.is_some(),
ast::ExpressionKind::ObjectPattern(obj) => obj.type_annotation.is_some(),
_ => false,
};
(!has_type_ann).then_some(ap)
} else {
None
};
node_header(w, "TSParameterProperty", expr.span, ctx);
if let Some(acc) = param_prop.accessibility {
w.raw(",\"accessibility\":");
w.token(acc.as_str());
}
if param_prop.readonly {
w.raw(",\"readonly\":true");
}
if param_prop.r#override {
w.raw(",\"override\":true");
}
w.raw(",\"parameter\":");
if let Some(ap) = override_ap {
write_assignment_pattern(w, ap, ctx, expr.span);
} else {
write_expression(w, param_prop.parameter, ctx);
}
close_node(w, "TSParameterProperty", expr.span, ctx);
}
}
}
enum ChainTail<'e> {
Call {
call: &'e ast::CallExpression<'e>,
callee_chain: ChainState,
force: bool,
strip: bool,
},
Member {
member: &'e ast::MemberExpression<'e>,
force: bool,
strip: bool,
},
NonNull,
}
#[inline(never)]
fn write_chain(w: &mut JsonWriter, expr: &ast::Expression<'_>, ctx: &Ctx<'_>, flags: ExprFlags) {
let mut tails: SmallVec<[(ChainTail<'_>, Span, bool); 8]> = SmallVec::new();
let (mut expr, mut flags) = (expr, flags);
loop {
let (child, child_flags, tail) = match &expr.kind {
ast::ExpressionKind::CallExpression(call) => {
let needs_chain = needs_chain_wrap(expr, flags);
let callee_chain = child_chain(
expr.span.start,
call.callee.span().start,
needs_chain,
flags,
);
let (force, strip) = flags_after_wrap(flags, needs_chain);
open_chain_link(
w,
needs_chain,
"CallExpression",
",\"callee\":",
expr.span,
ctx,
);
(
call.callee,
call_callee_flags(call, callee_chain, strip),
(
ChainTail::Call {
call,
callee_chain,
force,
strip,
},
needs_chain,
),
)
}
ast::ExpressionKind::MemberExpression(member) => {
let needs_chain = needs_chain_wrap(expr, flags);
let object_chain = child_chain(
expr.span.start,
member.object.span().start,
needs_chain,
flags,
);
let (force, strip) = flags_after_wrap(flags, needs_chain);
open_chain_link(
w,
needs_chain,
"MemberExpression",
",\"object\":",
expr.span,
ctx,
);
(
member.object,
ExprFlags {
chain: object_chain,
force_optional: false,
strip_optional: strip,
},
(
ChainTail::Member {
member,
force,
strip,
},
needs_chain,
),
)
}
ast::ExpressionKind::TSNonNullExpression(non_null) => {
let needs_chain = needs_chain_wrap(expr, flags);
let inner_chain = child_chain(
expr.span.start,
non_null.expression.span().start,
needs_chain,
flags,
);
open_chain_link(
w,
needs_chain,
"TSNonNullExpression",
",\"expression\":",
expr.span,
ctx,
);
(
non_null.expression,
ExprFlags {
chain: inner_chain,
..ExprFlags::default()
},
(ChainTail::NonNull, needs_chain),
)
}
_ => {
write_expression_inner(w, expr, ctx, flags);
break;
}
};
tails.push((tail.0, expr.span, tail.1));
expr = child;
flags = child_flags;
}
for (tail, span, needs_chain) in tails.into_iter().rev() {
match tail {
ChainTail::Call {
call,
callee_chain,
force,
strip,
} => write_call_expression_tail(w, call, span, ctx, callee_chain, force, strip),
ChainTail::Member {
member,
force,
strip,
} => write_member_expression_tail(w, member, span, ctx, force, strip),
ChainTail::NonNull => close_node(w, "TSNonNullExpression", span, ctx),
}
if needs_chain {
close_node(w, "ChainExpression", span, ctx);
}
}
}
fn open_chain_link(
w: &mut JsonWriter,
needs_chain: bool,
node_type: &'static str,
child_key: &'static str,
span: Span,
ctx: &Ctx<'_>,
) {
if needs_chain {
node_header(w, "ChainExpression", span, ctx);
w.raw(",\"expression\":");
}
node_header(w, node_type, span, ctx);
w.raw(child_key);
}
fn needs_chain_wrap(expr: &ast::Expression<'_>, flags: ExprFlags) -> bool {
flags.chain == ChainState::Unresolved && expr.has_optional_in_chain()
}
fn child_chain(
parent_start: u32,
child_start: u32,
needs_chain: bool,
flags: ExprFlags,
) -> ChainState {
if parent_start < child_start {
return ChainState::Unresolved;
}
if needs_chain || flags.chain == ChainState::InChain {
ChainState::InChain
} else {
ChainState::KnownFree
}
}
fn flags_after_wrap(flags: ExprFlags, needs_chain: bool) -> (bool, bool) {
if needs_chain {
(false, false)
} else {
(flags.force_optional, flags.strip_optional)
}
}
fn write_object_property(w: &mut JsonWriter, prop: &ast::ObjectProperty<'_>, ctx: &Ctx<'_>) {
match prop {
ast::ObjectProperty::Property(p) => write_property(w, p, ctx),
ast::ObjectProperty::SpreadElement(s) => {
node_header(w, "SpreadElement", s.span, ctx);
w.raw(",\"argument\":");
write_expression(w, s.argument, ctx);
close_node(w, "SpreadElement", s.span, ctx);
}
}
}