#![expect(clippy::inline_always)]
use std::mem;
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, ConfigFixesJS, ConfigFixesTS, ConfigJS, ConfigTS};
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 INCLUDE_TS_FIELDS: bool;
const IS_COMPACT: bool = Self::Formatter::IS_COMPACT;
type Formatter: Formatter;
type StructSerializer: StructSerializer;
type SequenceSerializer: SequenceSerializer;
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 CompactTSSerializer = ESTreeSerializer<ConfigTS, CompactFormatter>;
pub type CompactJSSerializer = ESTreeSerializer<ConfigJS, CompactFormatter>;
pub type PrettyTSSerializer = ESTreeSerializer<ConfigTS, PrettyFormatter>;
pub type PrettyJSSerializer = ESTreeSerializer<ConfigJS, PrettyFormatter>;
pub type CompactFixesTSSerializer = ESTreeSerializer<ConfigFixesTS, CompactFormatter>;
pub type CompactFixesJSSerializer = ESTreeSerializer<ConfigFixesJS, CompactFormatter>;
pub type PrettyFixesTSSerializer = ESTreeSerializer<ConfigFixesTS, PrettyFormatter>;
pub type PrettyFixesJSSerializer = ESTreeSerializer<ConfigFixesJS, 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(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(ranges),
}
}
pub fn with_capacity(capacity: usize, 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(ranges),
}
}
pub fn serialize_with_fixes<T: ESTree>(mut self, node: &T) -> String {
const {
assert!(
C::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 traces_buffer = mem::take(&mut self.fixes_buffer).into_string();
self.buffer.print_str(&traces_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(false)
}
}
impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer<C, F> {
const INCLUDE_TS_FIELDS: bool = C::INCLUDE_TS_FIELDS;
type Formatter = F;
type StructSerializer = ESTreeStructSerializer<'s, C, F>;
type SequenceSerializer = ESTreeSequenceSerializer<'s, C, F>;
#[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 !C::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);
}