Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

9 changes: 9 additions & 0 deletions crates/oxc_data_structures/src/code_buffer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8> {
&mut self.buf
}

/// Returns the capacity of the buffer in bytes.
///
/// This is *not* the same as capacity in characters,
Expand Down
6 changes: 6 additions & 0 deletions crates/oxc_estree/src/serialize/formatter.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -71,6 +75,8 @@ pub struct PrettyFormatter {
}

impl Formatter for PrettyFormatter {
const IS_COMPACT: bool = false;

#[inline(always)]
fn new() -> Self {
Self { indent: 0 }
Expand Down
18 changes: 6 additions & 12 deletions crates/oxc_estree/src/serialize/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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;
Expand Down Expand Up @@ -181,6 +177,8 @@ impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer<C, F>
/// `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>;

Expand Down Expand Up @@ -235,10 +233,6 @@ impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer<C, F>

self.fixes_buffer.print_ascii_byte(b']');
}
}

impl<C: Config, F: Formatter> SerializerPrivate for &mut ESTreeSerializer<C, F> {
type Formatter = F;

/// Get mutable reference to buffer.
#[inline(always)]
Expand Down
4 changes: 1 addition & 3 deletions crates/oxc_estree/src/serialize/sequences.rs
Original file line number Diff line number Diff line change
@@ -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 {
Expand Down
8 changes: 3 additions & 5 deletions crates/oxc_estree/src/serialize/structs.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down Expand Up @@ -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>;

Expand All @@ -247,10 +249,6 @@ impl<'p, P: StructSerializer> Serializer for FlatStructSerializer<'p, P> {
fn ranges(&self) -> bool {
self.0.ranges()
}
}

impl<P: StructSerializer> SerializerPrivate for FlatStructSerializer<'_, P> {
type Formatter = P::Formatter;

fn buffer_mut(&mut self) -> &mut CodeBuffer {
const {
Expand Down
1 change: 1 addition & 0 deletions crates/oxc_estree_tokens/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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 }
80 changes: 78 additions & 2 deletions crates/oxc_estree_tokens/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
use std::slice::Iter;

use itoa::Buffer as ItoaBuffer;

use oxc_ast::ast::*;
use oxc_ast_visit::{
Visit,
utf8_to_utf16::{Utf8ToUtf16, Utf8ToUtf16Converter},
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};
Expand Down Expand Up @@ -90,7 +92,81 @@ struct EstreeIdentToken<'a> {
}

impl ESTree for EstreeIdentToken<'_> {
fn serialize<S: Serializer>(&self, serializer: S) {
fn serialize<S: Serializer>(&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));
Expand Down
Loading