diff --git a/Cargo.lock b/Cargo.lock index a9fa988812383..61ff9cb2aced2 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1907,6 +1907,7 @@ dependencies = [ name = "oxc_estree_tokens" version = "0.115.0" dependencies = [ + "itoa", "oxc_ast", "oxc_ast_visit", "oxc_estree", diff --git a/crates/oxc_data_structures/src/code_buffer.rs b/crates/oxc_data_structures/src/code_buffer.rs index b194821a736e0..8dbfaab976d86 100644 --- a/crates/oxc_data_structures/src/code_buffer.rs +++ b/crates/oxc_data_structures/src/code_buffer.rs @@ -147,6 +147,15 @@ impl CodeBuffer { self.buf.len() } + /// Obtain the underlying buffer. + /// + /// # SAFETY + /// Caller must ensure that any mutations to the buffer leave it containing a valid UTF-8 string. + #[inline] + pub unsafe fn inner_mut(&mut self) -> &mut Vec { + &mut self.buf + } + /// Returns the capacity of the buffer in bytes. /// /// This is *not* the same as capacity in characters, diff --git a/crates/oxc_estree/src/serialize/formatter.rs b/crates/oxc_estree/src/serialize/formatter.rs index d0c60911a03ff..a9552fe069560 100644 --- a/crates/oxc_estree/src/serialize/formatter.rs +++ b/crates/oxc_estree/src/serialize/formatter.rs @@ -2,6 +2,8 @@ use oxc_data_structures::code_buffer::CodeBuffer; /// Formatter trait. pub trait Formatter { + const IS_COMPACT: bool; + /// Create new [`Formatter`]. fn new() -> Self; @@ -30,6 +32,8 @@ pub trait Formatter { pub struct CompactFormatter; impl Formatter for CompactFormatter { + const IS_COMPACT: bool = true; + #[inline(always)] fn new() -> Self { Self @@ -71,6 +75,8 @@ pub struct PrettyFormatter { } impl Formatter for PrettyFormatter { + const IS_COMPACT: bool = false; + #[inline(always)] fn new() -> Self { Self { indent: 0 } diff --git a/crates/oxc_estree/src/serialize/mod.rs b/crates/oxc_estree/src/serialize/mod.rs index 13e90cfbbd4f9..59692a98ade4f 100644 --- a/crates/oxc_estree/src/serialize/mod.rs +++ b/crates/oxc_estree/src/serialize/mod.rs @@ -37,11 +37,13 @@ pub trait ESTree { // // This trait contains public methods. // Internal methods we don't want to expose outside this crate are in [`SerializerPrivate`] trait. -#[expect(private_bounds)] -pub trait Serializer: SerializerPrivate { +pub trait Serializer { /// `true` if output should contain TS fields const INCLUDE_TS_FIELDS: bool; + /// Formatter type + type Formatter: Formatter; + /// Type of struct serializer this serializer uses. type StructSerializer: StructSerializer; /// Type of sequence serializer this serializer uses. @@ -62,12 +64,6 @@ pub trait Serializer: SerializerPrivate { /// These nodes cannot be serialized to JSON, because JSON doesn't support `BigInt`s or `RegExp`s. /// "Fix paths" can be used on JS side to locate these nodes and set their `value` fields correctly. fn record_fix_path(&mut self); -} - -/// Trait containing internal methods of [`Serializer`]s that we don't want to expose outside this crate. -trait SerializerPrivate: Sized { - /// Formatter type - type Formatter: Formatter; /// Get mutable reference to buffer. fn buffer_mut(&mut self) -> &mut CodeBuffer; @@ -181,6 +177,8 @@ impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer /// `true` if output should contain TS fields const INCLUDE_TS_FIELDS: bool = C::INCLUDE_TS_FIELDS; + type Formatter = F; + type StructSerializer = ESTreeStructSerializer<'s, C, F>; type SequenceSerializer = ESTreeSequenceSerializer<'s, C, F>; @@ -235,10 +233,6 @@ impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer self.fixes_buffer.print_ascii_byte(b']'); } -} - -impl SerializerPrivate for &mut ESTreeSerializer { - type Formatter = F; /// Get mutable reference to buffer. #[inline(always)] diff --git a/crates/oxc_estree/src/serialize/sequences.rs b/crates/oxc_estree/src/serialize/sequences.rs index 119cf58140935..65f242d373aa8 100644 --- a/crates/oxc_estree/src/serialize/sequences.rs +++ b/crates/oxc_estree/src/serialize/sequences.rs @@ -1,6 +1,4 @@ -use super::{ - Config, ESTree, ESTreeSerializer, Formatter, Serializer, SerializerPrivate, TracePathPart, -}; +use super::{Config, ESTree, ESTreeSerializer, Formatter, Serializer, TracePathPart}; /// Trait for sequence serializers. pub trait SequenceSerializer { diff --git a/crates/oxc_estree/src/serialize/structs.rs b/crates/oxc_estree/src/serialize/structs.rs index bfc10e75ff3ba..d757d1850c429 100644 --- a/crates/oxc_estree/src/serialize/structs.rs +++ b/crates/oxc_estree/src/serialize/structs.rs @@ -2,7 +2,7 @@ use oxc_data_structures::code_buffer::CodeBuffer; use super::{ Config, ESTree, ESTreeSequenceSerializer, ESTreeSerializer, Formatter, Serializer, - SerializerPrivate, TracePathPart, + TracePathPart, }; /// Trait for struct serializers. @@ -222,6 +222,8 @@ impl<'p, P: StructSerializer> Serializer for FlatStructSerializer<'p, P> { /// `true` if output should contain TS fields const INCLUDE_TS_FIELDS: bool = P::Config::INCLUDE_TS_FIELDS; + type Formatter = P::Formatter; + type StructSerializer = Self; type SequenceSerializer = ESTreeSequenceSerializer<'p, P::Config, P::Formatter>; @@ -247,10 +249,6 @@ impl<'p, P: StructSerializer> Serializer for FlatStructSerializer<'p, P> { fn ranges(&self) -> bool { self.0.ranges() } -} - -impl SerializerPrivate for FlatStructSerializer<'_, P> { - type Formatter = P::Formatter; fn buffer_mut(&mut self) -> &mut CodeBuffer { const { diff --git a/crates/oxc_estree_tokens/Cargo.toml b/crates/oxc_estree_tokens/Cargo.toml index 22e8d56d1b80b..0964c76f60a8b 100644 --- a/crates/oxc_estree_tokens/Cargo.toml +++ b/crates/oxc_estree_tokens/Cargo.toml @@ -25,3 +25,4 @@ oxc_ast_visit = { workspace = true, features = ["serialize"] } oxc_estree = { workspace = true, features = ["serialize"] } oxc_parser = { workspace = true } oxc_span = { workspace = true, features = ["serialize"] } +itoa = { workspace = true } diff --git a/crates/oxc_estree_tokens/src/lib.rs b/crates/oxc_estree_tokens/src/lib.rs index 7c089ba39588e..2489352fc4073 100644 --- a/crates/oxc_estree_tokens/src/lib.rs +++ b/crates/oxc_estree_tokens/src/lib.rs @@ -1,5 +1,7 @@ use std::slice::Iter; +use itoa::Buffer as ItoaBuffer; + use oxc_ast::ast::*; use oxc_ast_visit::{ Visit, @@ -7,7 +9,7 @@ use oxc_ast_visit::{ walk, }; use oxc_estree::{ - CompactFormatter, Config, ESTree, ESTreeSerializer, JsonSafeString, PrettyFormatter, + CompactFormatter, Config, ESTree, ESTreeSerializer, Formatter, JsonSafeString, PrettyFormatter, SequenceSerializer, Serializer, StructSerializer, }; use oxc_parser::{Kind, Token}; @@ -90,7 +92,81 @@ struct EstreeIdentToken<'a> { } impl ESTree for EstreeIdentToken<'_> { - fn serialize(&self, serializer: S) { + fn serialize(&self, mut serializer: S) { + if S::Formatter::IS_COMPACT { + static STR: &str = "{\"type\":\",\"value\":\",\"start\":,\"end\":"; + static TYPE_PREFIX: &str = "{\"type\":\""; + static VALUE_PREFIX: &str = "\",\"value\":\""; + static START_PREFIX: &str = "\",\"start\":"; + static END_PREFIX: &str = ",\"end\":"; + static POSTFIX: u8 = b'}'; + const MAX_U32_LEN: usize = 10; + + // SAFETY: We push only valid UTF-8 strings to `buffer` + let buffer = unsafe { serializer.buffer_mut().inner_mut() }; + + // +2 for the 2 overlapping `"`s + // +1 for the following comma + buffer.reserve( + STR.len() + + 2 + + 1 + + MAX_U32_LEN + + MAX_U32_LEN + + self.token_type.len() + + self.value.len(), + ); + + // SAFETY: So safe! + unsafe { + let start_ptr = buffer.as_mut_ptr(); + let mut ptr = start_ptr.add(buffer.len()); + + // type + ptr.copy_from_nonoverlapping(STR.as_ptr(), 16); + ptr.add(TYPE_PREFIX.len()) + .copy_from_nonoverlapping(self.token_type.as_ptr(), self.token_type.len()); + ptr = ptr.add(TYPE_PREFIX.len() + self.token_type.len()); + + // value + ptr.copy_from_nonoverlapping(STR.as_ptr().add(TYPE_PREFIX.len() - 1), 16); + ptr.add(VALUE_PREFIX.len()) + .copy_from_nonoverlapping(self.value.as_ptr(), self.value.len()); + ptr = ptr.add(VALUE_PREFIX.len() + self.value.len()); + + // start + ptr.copy_from_nonoverlapping( + STR.as_ptr().add(TYPE_PREFIX.len() - 1 + VALUE_PREFIX.len() - 1), + 16, + ); + let mut itoa_buffer = ItoaBuffer::new(); + let start_str = itoa_buffer.format(self.span.start); + // TODO: This is UB. Not all bytes of the buffer are initialized. + ptr.add(START_PREFIX.len()).copy_from_nonoverlapping(start_str.as_ptr(), 16); + ptr = ptr.add(START_PREFIX.len() + start_str.len()); + + // end + ptr.copy_from_nonoverlapping( + STR.as_ptr() + .add(TYPE_PREFIX.len() - 1 + VALUE_PREFIX.len() - 1 + START_PREFIX.len()), + 8, + ); + let end_str = itoa_buffer.format(self.span.end); + // TODO: This is UB. Not all bytes of the buffer are initialized. + ptr.add(END_PREFIX.len()).copy_from_nonoverlapping(end_str.as_ptr(), 16); + ptr = ptr.add(END_PREFIX.len() + end_str.len()); + + // postfix + ptr.write(POSTFIX); + ptr = ptr.add(1); + + let new_len = ptr.offset_from_unsigned(start_ptr); + buffer.set_len(new_len); + } + + return; + } + let mut state = serializer.serialize_struct(); state.serialize_field("type", &JsonSafeString(self.token_type)); state.serialize_field("value", &JsonSafeString(self.value));