#![expect(clippy::inline_always)]
use itoa::Buffer as ItoaBuffer;
use oxc_data_structures::{
code_buffer::{CodeBuffer, IndentChar},
stack::NonEmptyStack,
};
mod blanket;
mod concat;
mod config;
mod formatter;
mod primitives;
mod sequences;
mod strings;
mod structs;
use sequences::ESTreeSequenceSerializer;
use structs::ESTreeStructSerializer;
pub use concat::{Concat2, Concat3, ConcatElement};
pub use config::{Config, ConfigFixes, ConfigNoFixes};
pub use formatter::{CompactFormatter, Formatter, PrettyFormatter};
pub use sequences::SequenceSerializer;
pub use strings::{JsonSafeString, LoneSurrogatesString};
pub use structs::{ESTreeSpan, FlatStructSerializer, StructSerializer};
pub trait ESTree {
fn serialize<S: Serializer>(&self, serializer: S);
}
pub trait Serializer {
const IS_COMPACT: bool = Self::Formatter::IS_COMPACT;
type Formatter: Formatter;
type StructSerializer: StructSerializer;
type SequenceSerializer: SequenceSerializer;
fn include_ts_fields(&self) -> bool;
fn ranges(&self) -> bool;
fn serialize_struct(self) -> Self::StructSerializer;
fn serialize_sequence(self) -> Self::SequenceSerializer;
fn record_fix_path(&mut self);
fn buffer_mut(&mut self) -> &mut CodeBuffer;
fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut Self::Formatter);
}
pub type CompactSerializer = ESTreeSerializer<ConfigNoFixes, CompactFormatter>;
pub type PrettySerializer = ESTreeSerializer<ConfigNoFixes, PrettyFormatter>;
pub type CompactFixesSerializer = ESTreeSerializer<ConfigFixes, CompactFormatter>;
pub type PrettyFixesSerializer = ESTreeSerializer<ConfigFixes, PrettyFormatter>;
pub struct ESTreeSerializer<C: Config, F: Formatter> {
buffer: CodeBuffer,
formatter: F,
trace_path: NonEmptyStack<TracePathPart>,
fixes_buffer: CodeBuffer,
config: C,
}
impl<C: Config, F: Formatter> ESTreeSerializer<C, F> {
pub fn new(include_ts_fields: bool, ranges: bool) -> Self {
Self {
buffer: CodeBuffer::with_indent(IndentChar::Space, 2),
formatter: F::new(),
trace_path: NonEmptyStack::new(TracePathPart::Index(0)),
fixes_buffer: CodeBuffer::new(),
config: C::new(include_ts_fields, ranges),
}
}
pub fn with_capacity(capacity: usize, include_ts_fields: bool, ranges: bool) -> Self {
Self {
buffer: CodeBuffer::with_capacity_and_indent(capacity, IndentChar::Space, 2),
formatter: F::new(),
trace_path: NonEmptyStack::new(TracePathPart::Index(0)),
fixes_buffer: CodeBuffer::new(),
config: C::new(include_ts_fields, ranges),
}
}
pub fn serialize_with_fixes<T: ESTree>(mut self, node: &T) -> String {
assert!(
self.config.fixes(),
"Cannot call `serialize_with_fixes` on a serializer without fixes enabled"
);
self.buffer.print_str("{\"node\":\n");
node.serialize(&mut self);
debug_assert!(self.trace_path.is_exhausted());
debug_assert_eq!(self.trace_path[0], TracePathPart::DUMMY);
self.buffer.print_str("\n,\"fixes\":[");
if !self.fixes_buffer.is_empty() {
let fixes_buffer = self.fixes_buffer.as_str();
self.buffer.print_str(&fixes_buffer[1..]);
}
self.buffer.print_str("]}");
self.buffer.into_string()
}
pub fn into_string(self) -> String {
self.buffer.into_string()
}
}
impl<C: Config, F: Formatter> Default for ESTreeSerializer<C, F> {
#[inline(always)]
fn default() -> Self {
Self::new(true, false)
}
}
impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer<C, F> {
type Formatter = F;
type StructSerializer = ESTreeStructSerializer<'s, C, F>;
type SequenceSerializer = ESTreeSequenceSerializer<'s, C, F>;
#[inline(always)]
fn include_ts_fields(&self) -> bool {
self.config.include_ts_fields()
}
#[inline(always)]
fn ranges(&self) -> bool {
self.config.ranges()
}
#[inline(always)]
fn serialize_struct(self) -> ESTreeStructSerializer<'s, C, F> {
ESTreeStructSerializer::new(self)
}
#[inline(always)]
fn serialize_sequence(self) -> ESTreeSequenceSerializer<'s, C, F> {
ESTreeSequenceSerializer::new(self)
}
fn record_fix_path(&mut self) {
if !self.config.fixes() {
return;
}
self.fixes_buffer.print_str(",[");
let parts = self.trace_path.as_slice();
let parts = &parts[1..parts.len() - 1];
for (index, part) in parts.iter().enumerate() {
if index > 0 {
self.fixes_buffer.print_ascii_byte(b',');
}
match *part {
TracePathPart::Key(key) => {
self.fixes_buffer.print_strs_array(["\"", key, "\""]);
}
TracePathPart::Index(index) => {
let mut buffer = ItoaBuffer::new();
let s = buffer.format(index);
self.fixes_buffer.print_str(s);
}
}
}
self.fixes_buffer.print_ascii_byte(b']');
}
#[inline(always)]
fn buffer_mut(&mut self) -> &mut CodeBuffer {
&mut self.buffer
}
#[inline(always)]
fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut F) {
(&mut self.buffer, &mut self.formatter)
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum TracePathPart {
Key(&'static str),
Index(usize),
}
impl TracePathPart {
pub const DUMMY: Self = TracePathPart::Index(0);
}