diff --git a/Cargo.lock b/Cargo.lock index 7411f71e08691b..4073a0f2d2c8c1 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1759,6 +1759,7 @@ name = "oxc_estree" version = "0.52.0" dependencies = [ "itoa", + "oxc_data_structures", "ryu-js", ] diff --git a/crates/oxc_estree/Cargo.toml b/crates/oxc_estree/Cargo.toml index 9251f49d41f415..2b8e721c3b7e9d 100644 --- a/crates/oxc_estree/Cargo.toml +++ b/crates/oxc_estree/Cargo.toml @@ -19,6 +19,8 @@ workspace = true doctest = false [dependencies] +oxc_data_structures = { workspace = true } + itoa = { workspace = true } ryu-js = { workspace = true, optional = true } diff --git a/crates/oxc_estree/src/serialize/blanket.rs b/crates/oxc_estree/src/serialize/blanket.rs index 97177e64900b29..99bb174a3f1ec6 100644 --- a/crates/oxc_estree/src/serialize/blanket.rs +++ b/crates/oxc_estree/src/serialize/blanket.rs @@ -30,7 +30,7 @@ impl ESTree for Option { if let Some(value) = self { value.serialize(serializer); } else { - serializer.buffer_mut().push_str("null"); + serializer.buffer_mut().print_str("null"); } } } diff --git a/crates/oxc_estree/src/serialize/buffer.rs b/crates/oxc_estree/src/serialize/buffer.rs deleted file mode 100644 index d9dbdf910a7c1b..00000000000000 --- a/crates/oxc_estree/src/serialize/buffer.rs +++ /dev/null @@ -1,51 +0,0 @@ -/// Buffer to store serialized JSON. -pub struct Buffer { - bytes: Vec, -} - -impl Buffer { - /// Create new [`Buffer`]. - pub(super) fn new() -> Self { - Self { bytes: vec![] } - } - - /// Push a single ASCII byte to the buffer. - /// - /// # Panics - /// Panics if `byte` is not ASCII. - #[inline(always)] - pub(super) fn push_ascii_byte(&mut self, byte: u8) { - assert!(byte.is_ascii()); - // SAFETY: We just checked this byte is ASCII - unsafe { self.push_ascii_byte_unchecked(byte) }; - } - - /// Push a single ASCII byte to the buffer, without checking that it's ASCII. - /// - /// # SAFETY - /// `byte` must be ASCII. - #[inline(always)] - pub(super) unsafe fn push_ascii_byte_unchecked(&mut self, byte: u8) { - self.bytes.push(byte); - } - - /// Push a raw string to the buffer. - pub(super) fn push_str(&mut self, s: &str) { - self.bytes.extend_from_slice(s.as_bytes()); - } - - /// Push a slice of bytes to the buffer. - /// - /// # SAFETY - /// `bytes` must comprise a valid UTF-8 string. - pub(super) unsafe fn push_bytes(&mut self, bytes: &[u8]) { - self.bytes.extend_from_slice(bytes); - } - - /// Consume [`Buffer`] and convert buffer to string. - pub(super) fn into_string(self) -> String { - // SAFETY: None of `Serializer`'s safe methods allow - // adding invalid byte sequences to `self.bytes` - unsafe { String::from_utf8_unchecked(self.bytes) } - } -} diff --git a/crates/oxc_estree/src/serialize/formatter.rs b/crates/oxc_estree/src/serialize/formatter.rs index 7463451160a003..5cd59c61887305 100644 --- a/crates/oxc_estree/src/serialize/formatter.rs +++ b/crates/oxc_estree/src/serialize/formatter.rs @@ -1,4 +1,4 @@ -use super::Buffer; +use oxc_data_structures::code_buffer::CodeBuffer; /// Formatter trait. pub trait Formatter { @@ -7,18 +7,18 @@ pub trait Formatter { /// Called before the first field of a struct or element of a sequence. /// If the struct/sequence has no fields/elements, this is not called. - fn before_first_element(&mut self, buffer: &mut Buffer); + fn before_first_element(&mut self, buffer: &mut CodeBuffer); /// Called before a later field of a struct or element of a sequence /// (i.e. not the first field/element). - fn before_later_element(&mut self, buffer: &mut Buffer); + fn before_later_element(&mut self, buffer: &mut CodeBuffer); /// Called after the key of a struct field. - fn before_field_value(&mut self, buffer: &mut Buffer); + fn before_field_value(&mut self, buffer: &mut CodeBuffer); /// Called after the last element of a sequence / last element of a struct. /// If the struct/sequence has no fields/elements, this is not called. - fn after_last_element(&mut self, buffer: &mut Buffer); + fn after_last_element(&mut self, buffer: &mut CodeBuffer); } /// Compact formatter. @@ -36,16 +36,16 @@ impl Formatter for CompactFormatter { } #[inline(always)] - fn before_first_element(&mut self, _buffer: &mut Buffer) {} + fn before_first_element(&mut self, _buffer: &mut CodeBuffer) {} #[inline(always)] - fn before_later_element(&mut self, _buffer: &mut Buffer) {} + fn before_later_element(&mut self, _buffer: &mut CodeBuffer) {} #[inline(always)] - fn before_field_value(&mut self, _buffer: &mut Buffer) {} + fn before_field_value(&mut self, _buffer: &mut CodeBuffer) {} #[inline(always)] - fn after_last_element(&mut self, _buffer: &mut Buffer) {} + fn after_last_element(&mut self, _buffer: &mut CodeBuffer) {} } /// Pretty-print formatter. @@ -76,29 +76,29 @@ impl Formatter for PrettyFormatter { Self { indent: 0 } } - fn before_first_element(&mut self, buffer: &mut Buffer) { + fn before_first_element(&mut self, buffer: &mut CodeBuffer) { self.indent += 1; self.push_new_line_and_indent(buffer); } - fn before_later_element(&mut self, buffer: &mut Buffer) { + fn before_later_element(&mut self, buffer: &mut CodeBuffer) { self.push_new_line_and_indent(buffer); } - fn before_field_value(&mut self, buffer: &mut Buffer) { - buffer.push_ascii_byte(b' '); + fn before_field_value(&mut self, buffer: &mut CodeBuffer) { + buffer.print_ascii_byte(b' '); } - fn after_last_element(&mut self, buffer: &mut Buffer) { + fn after_last_element(&mut self, buffer: &mut CodeBuffer) { self.indent -= 1; self.push_new_line_and_indent(buffer); } } impl PrettyFormatter { - fn push_new_line_and_indent(&self, buffer: &mut Buffer) { - buffer.push_ascii_byte(b'\n'); + fn push_new_line_and_indent(&self, buffer: &mut CodeBuffer) { + buffer.print_ascii_byte(b'\n'); // SAFETY: Spaces are ASCII - unsafe { buffer.push_bytes(&b" ".repeat(self.indent)) }; + unsafe { buffer.print_bytes_unchecked(&b" ".repeat(self.indent)) }; } } diff --git a/crates/oxc_estree/src/serialize/mod.rs b/crates/oxc_estree/src/serialize/mod.rs index c483673766f6f4..ca28359cb86f45 100644 --- a/crates/oxc_estree/src/serialize/mod.rs +++ b/crates/oxc_estree/src/serialize/mod.rs @@ -1,15 +1,15 @@ // Methods which are trivial or just delegate to other methods are marked `#[inline(always)]` #![expect(clippy::inline_always)] +use oxc_data_structures::code_buffer::CodeBuffer; + mod blanket; -mod buffer; mod config; mod formatter; mod primitives; mod sequences; mod strings; mod structs; -use buffer::Buffer; use config::{Config, ConfigJS, ConfigTS}; use formatter::{CompactFormatter, Formatter, PrettyFormatter}; use sequences::ESTreeSequenceSerializer; @@ -47,10 +47,10 @@ trait SerializerPrivate: Sized { type Formatter: Formatter; /// Get mutable reference to buffer. - fn buffer_mut(&mut self) -> &mut Buffer; + fn buffer_mut(&mut self) -> &mut CodeBuffer; /// Get mutable references to buffer and formatter. - fn buffer_and_formatter_mut(&mut self) -> (&mut Buffer, &mut Self::Formatter); + fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut Self::Formatter); } /// ESTree serializer which produces compact JSON, including TypeScript fields. @@ -67,7 +67,7 @@ pub type PrettyJSSerializer = ESTreeSerializer; /// ESTree serializer. pub struct ESTreeSerializer { - buffer: Buffer, + buffer: CodeBuffer, formatter: F, #[expect(unused)] config: C, @@ -76,7 +76,7 @@ pub struct ESTreeSerializer { impl ESTreeSerializer { /// Create new [`ESTreeSerializer`]. pub fn new() -> Self { - Self { buffer: Buffer::new(), formatter: F::new(), config: C::new() } + Self { buffer: CodeBuffer::new(), formatter: F::new(), config: C::new() } } /// Consume this [`ESTreeSerializer`] and convert buffer to string. @@ -114,13 +114,13 @@ impl SerializerPrivate for &mut ESTreeSerializer /// Get mutable reference to buffer. #[inline(always)] - fn buffer_mut(&mut self) -> &mut Buffer { + fn buffer_mut(&mut self) -> &mut CodeBuffer { &mut self.buffer } /// Get mutable references to buffer and formatter. #[inline(always)] - fn buffer_and_formatter_mut(&mut self) -> (&mut Buffer, &mut F) { + fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut F) { (&mut self.buffer, &mut self.formatter) } } diff --git a/crates/oxc_estree/src/serialize/primitives.rs b/crates/oxc_estree/src/serialize/primitives.rs index dd306b39c98394..9b2ecec1cfca6f 100644 --- a/crates/oxc_estree/src/serialize/primitives.rs +++ b/crates/oxc_estree/src/serialize/primitives.rs @@ -6,7 +6,7 @@ use super::{ESTree, Serializer}; /// [`ESTree`] implementation for `bool`. impl ESTree for bool { fn serialize(&self, mut serializer: S) { - serializer.buffer_mut().push_str(if *self { "true" } else { "false" }); + serializer.buffer_mut().print_str(if *self { "true" } else { "false" }); } } @@ -18,17 +18,17 @@ macro_rules! impl_float { if self.is_finite() { let mut buffer = RyuBuffer::new(); let s = buffer.format_finite(*self); - serializer.buffer_mut().push_str(s); + serializer.buffer_mut().print_str(s); } else if self.is_nan() { // Serialize `NAN` as `null` // TODO: Throw an error? Use a sentinel value? - serializer.buffer_mut().push_str("null"); + serializer.buffer_mut().print_str("null"); } else if *self == $ty::INFINITY { // Serialize `INFINITY` as `1e+400. `JSON.parse` deserializes this as `Infinity`. - serializer.buffer_mut().push_str("1e+400"); + serializer.buffer_mut().print_str("1e+400"); } else { // Serialize `-INFINITY` as `-1e+400`. `JSON.parse` deserializes this as `-Infinity`. - serializer.buffer_mut().push_str("-1e+400"); + serializer.buffer_mut().print_str("-1e+400"); } } } @@ -45,7 +45,7 @@ macro_rules! impl_integer { fn serialize(&self, mut serializer: S) { let mut buffer = ItoaBuffer::new(); let s = buffer.format(*self); - serializer.buffer_mut().push_str(s); + serializer.buffer_mut().print_str(s); } } }; @@ -67,7 +67,7 @@ impl_integer!(isize); /// [`ESTree`] implementation for `()`. impl ESTree for () { fn serialize(&self, mut serializer: S) { - serializer.buffer_mut().push_str("null"); + serializer.buffer_mut().print_str("null"); } } diff --git a/crates/oxc_estree/src/serialize/sequences.rs b/crates/oxc_estree/src/serialize/sequences.rs index 03ceb5b17ccf2c..142aeb6916f5fd 100644 --- a/crates/oxc_estree/src/serialize/sequences.rs +++ b/crates/oxc_estree/src/serialize/sequences.rs @@ -23,7 +23,7 @@ pub struct ESTreeSequenceSerializer<'s, C: Config, F: Formatter> { impl<'s, C: Config, F: Formatter> ESTreeSequenceSerializer<'s, C, F> { /// Create new [`ESTreeSequenceSerializer`]. pub(super) fn new(mut serializer: &'s mut ESTreeSerializer) -> Self { - serializer.buffer_mut().push_ascii_byte(b'['); + serializer.buffer_mut().print_ascii_byte(b'['); Self { serializer, state: SequenceState::Empty } } } @@ -36,7 +36,7 @@ impl SequenceSerializer for ESTreeSequenceSerializer<'_ self.state = SequenceState::HasEntries; formatter.before_first_element(buffer); } else { - buffer.push_ascii_byte(b','); + buffer.print_ascii_byte(b','); formatter.before_later_element(buffer); } @@ -49,7 +49,7 @@ impl SequenceSerializer for ESTreeSequenceSerializer<'_ if self.state == SequenceState::HasEntries { formatter.after_last_element(buffer); } - buffer.push_ascii_byte(b']'); + buffer.print_ascii_byte(b']'); } } diff --git a/crates/oxc_estree/src/serialize/strings.rs b/crates/oxc_estree/src/serialize/strings.rs index e43d7b0c5ee2d1..8e6f7bbc132ebb 100644 --- a/crates/oxc_estree/src/serialize/strings.rs +++ b/crates/oxc_estree/src/serialize/strings.rs @@ -1,4 +1,6 @@ -use super::{Buffer, ESTree, Serializer}; +use oxc_data_structures::code_buffer::CodeBuffer; + +use super::{ESTree, Serializer}; /// [`ESTree`] implementation for string slice. impl ESTree for str { @@ -62,8 +64,8 @@ static ESCAPE: [Escape; 256] = { /// Write string to buffer. /// String is wrapped in `"`s, and with any characters which are not valid in JSON escaped. #[inline(always)] -fn write_str(s: &str, buffer: &mut Buffer) { - buffer.push_ascii_byte(b'"'); +fn write_str(s: &str, buffer: &mut CodeBuffer) { + buffer.print_ascii_byte(b'"'); let bytes = s.as_bytes(); @@ -80,7 +82,7 @@ fn write_str(s: &str, buffer: &mut Buffer) { // Therefore current `index` must mark the end of a valid UTF8 character sequence. // `start` is either the start of string, or after an ASCII character, // therefore always the start of a valid UTF8 character sequence. - unsafe { buffer.push_bytes(&bytes[start..index]) }; + unsafe { buffer.print_bytes_unchecked(&bytes[start..index]) }; } write_char_escape(escape, byte, buffer); @@ -92,18 +94,18 @@ fn write_str(s: &str, buffer: &mut Buffer) { // SAFETY: `bytes` is derived from a `&str`. // `start` is either the start of string, or after an ASCII character, // therefore always the start of a valid UTF8 character sequence. - unsafe { buffer.push_bytes(&bytes[start..]) }; + unsafe { buffer.print_bytes_unchecked(&bytes[start..]) }; } - buffer.push_ascii_byte(b'"'); + buffer.print_ascii_byte(b'"'); } -fn write_char_escape(escape: Escape, byte: u8, buffer: &mut Buffer) { +fn write_char_escape(escape: Escape, byte: u8, buffer: &mut CodeBuffer) { #[expect(clippy::if_not_else)] if escape != Escape::UU { - buffer.push_ascii_byte(b'\\'); + buffer.print_ascii_byte(b'\\'); // SAFETY: All values of `Escape` are ASCII - unsafe { buffer.push_ascii_byte_unchecked(escape as u8) }; + unsafe { buffer.print_byte_unchecked(escape as u8) }; } else { static HEX_DIGITS: [u8; 16] = *b"0123456789abcdef"; let bytes = [ @@ -115,7 +117,7 @@ fn write_char_escape(escape: Escape, byte: u8, buffer: &mut Buffer) { HEX_DIGITS[(byte & 0xF) as usize], ]; // SAFETY: `bytes` contains only ASCII bytes - unsafe { buffer.push_bytes(&bytes) } + unsafe { buffer.print_bytes_unchecked(&bytes) } } } diff --git a/crates/oxc_estree/src/serialize/structs.rs b/crates/oxc_estree/src/serialize/structs.rs index 40197a4f611f09..45cdd3fabba875 100644 --- a/crates/oxc_estree/src/serialize/structs.rs +++ b/crates/oxc_estree/src/serialize/structs.rs @@ -1,5 +1,7 @@ +use oxc_data_structures::code_buffer::CodeBuffer; + use super::{ - Buffer, Config, ESTree, ESTreeSequenceSerializer, ESTreeSerializer, Formatter, Serializer, + Config, ESTree, ESTreeSequenceSerializer, ESTreeSerializer, Formatter, Serializer, SerializerPrivate, }; @@ -42,7 +44,7 @@ pub struct ESTreeStructSerializer<'s, C: Config, F: Formatter> { impl<'s, C: Config, F: Formatter> ESTreeStructSerializer<'s, C, F> { /// Create new [`ESTreeStructSerializer`]. pub(super) fn new(mut serializer: &'s mut ESTreeSerializer) -> Self { - serializer.buffer_mut().push_ascii_byte(b'{'); + serializer.buffer_mut().print_ascii_byte(b'{'); Self { serializer, state: StructState::Empty } } } @@ -60,13 +62,13 @@ impl StructSerializer for ESTreeStructSerializer<'_, C, self.state = StructState::HasFields; formatter.before_first_element(buffer); } else { - buffer.push_ascii_byte(b','); + buffer.print_ascii_byte(b','); formatter.before_later_element(buffer); } - buffer.push_ascii_byte(b'"'); - buffer.push_str(key); - buffer.push_str("\":"); + buffer.print_ascii_byte(b'"'); + buffer.print_str(key); + buffer.print_str("\":"); formatter.before_field_value(buffer); value.serialize(&mut *self.serializer); } @@ -94,7 +96,7 @@ impl StructSerializer for ESTreeStructSerializer<'_, C, if self.state == StructState::HasFields { formatter.after_last_element(buffer); } - buffer.push_ascii_byte(b'}'); + buffer.print_ascii_byte(b'}'); } } @@ -161,13 +163,13 @@ impl<'p, P: StructSerializer> Serializer for FlatStructSerializer<'p, P> { impl SerializerPrivate for FlatStructSerializer<'_, P> { type Formatter = P::Formatter; - fn buffer_mut(&mut self) -> &mut Buffer { + fn buffer_mut(&mut self) -> &mut CodeBuffer { const { panic!("Cannot flatten anything but a struct into another struct"); } } - fn buffer_and_formatter_mut(&mut self) -> (&mut Buffer, &mut P::Formatter) { + fn buffer_and_formatter_mut(&mut self) -> (&mut CodeBuffer, &mut P::Formatter) { const { panic!("Cannot flatten anything but a struct into another struct"); }