mirror of
https://github.com/oxc-project/oxc.git
synced 2026-11-04 10:29:58 +00:00
perf(napi/parser): faster fixup of BigInts and RegExps (#10820)
#10791 fixed the performance of `oxc-parser` NPM package by removing the reviver from `JSON.parse` call. However, it replaced it with a complete traversal of the AST on JS side to locate `Literal` nodes and update them. Instead, generate a list of paths to `Literal` nodes needing fixing on Rust side in `ESTree` serializer. e.g. for this program: ```js 123n; foo(/xyz/); ``` the fix paths are: ```json [ ["body", 0, "expression"], ["body", 1, "expression", "arguments", 2] ] ``` Having the location of nodes which need updating reduces work on JS side, as no need to search the entire AST. Running `pnpm run bench` (in `napi/parser`) locally shows between 8% and 15% speed-up from this change.
This commit is contained in:
1 parent
33969ac361
commit
49a6f97388
17 files changed
+309
-449
No files matched your search
@@ -5,20 +5,22 @@ use cow_utils::CowUtils;
|
||||
use crate::ast::*;
|
||||
use oxc_ast_macros::ast_meta;
|
||||
use oxc_estree::{
|
||||
CompactJSSerializer, CompactTSSerializer, ESTree, FlatStructSerializer, JsonSafeString,
|
||||
LoneSurrogatesString, PrettyJSSerializer, PrettyTSSerializer, SequenceSerializer, Serializer,
|
||||
StructSerializer, ser::AppendToConcat,
|
||||
CompactFixesJSSerializer, CompactFixesTSSerializer, CompactJSSerializer, CompactTSSerializer,
|
||||
ESTree, FlatStructSerializer, JsonSafeString, LoneSurrogatesString, PrettyFixesJSSerializer,
|
||||
PrettyFixesTSSerializer, PrettyJSSerializer, PrettyTSSerializer, SequenceSerializer,
|
||||
Serializer, StructSerializer, ser::AppendToConcat,
|
||||
};
|
||||
use oxc_span::GetSpan;
|
||||
|
||||
/// Main serialization methods for `Program`.
|
||||
///
|
||||
/// Note: 4 separate methods for the different serialization options, rather than 1 method
|
||||
/// with behavior controlled by flags (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool`)
|
||||
/// Note: 8 separate methods for the different serialization options, rather than 1 method
|
||||
/// with behavior controlled by flags
|
||||
/// (e.g. `fn to_estree_json(&self, with_ts: bool, pretty: bool, fixes: bool)`)
|
||||
/// to avoid bloating binary size.
|
||||
///
|
||||
/// Most consumers (and Oxc crates) will use only 1 of these methods, so we don't want to needlessly
|
||||
/// compile all 4 serializers when only 1 is used.
|
||||
/// compile all 8 serializers when only 1 is used.
|
||||
///
|
||||
/// Initial capacity for serializer's buffer is an estimate based on our benchmark fixtures
|
||||
/// of ratio of source text size to JSON size.
|
||||
@@ -70,6 +72,34 @@ impl Program<'_> {
|
||||
self.serialize(&mut serializer);
|
||||
serializer.into_string()
|
||||
}
|
||||
|
||||
/// Serialize AST to ESTree JSON, including TypeScript fields, with list of fixes.
|
||||
pub fn to_estree_ts_json_with_fixes(&self) -> String {
|
||||
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
|
||||
let serializer = CompactFixesTSSerializer::with_capacity(capacity);
|
||||
serializer.serialize_with_fixes(self)
|
||||
}
|
||||
|
||||
/// Serialize AST to ESTree JSON, without TypeScript fields, with list of fixes.
|
||||
pub fn to_estree_js_json_with_fixes(&self) -> String {
|
||||
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_COMPACT;
|
||||
let serializer = CompactFixesJSSerializer::with_capacity(capacity);
|
||||
serializer.serialize_with_fixes(self)
|
||||
}
|
||||
|
||||
/// Serialize AST to pretty-printed ESTree JSON, including TypeScript fields, with list of fixes.
|
||||
pub fn to_pretty_estree_ts_json_with_fixes(&self) -> String {
|
||||
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
|
||||
let serializer = PrettyFixesTSSerializer::with_capacity(capacity);
|
||||
serializer.serialize_with_fixes(self)
|
||||
}
|
||||
|
||||
/// Serialize AST to pretty-printed ESTree JSON, without TypeScript fields, with list of fixes.
|
||||
pub fn to_pretty_estree_js_json_with_fixes(&self) -> String {
|
||||
let capacity = self.source_text.len() * JSON_CAPACITY_RATIO_PRETTY;
|
||||
let serializer = PrettyFixesJSSerializer::with_capacity(capacity);
|
||||
serializer.serialize_with_fixes(self)
|
||||
}
|
||||
}
|
||||
|
||||
// --------------------
|
||||
@@ -409,7 +439,9 @@ impl ESTree for BigIntLiteralBigint<'_, '_> {
|
||||
pub struct BigIntLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b BigIntLiteral<'a>);
|
||||
|
||||
impl ESTree for BigIntLiteralValue<'_, '_> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) {
|
||||
fn serialize<S: Serializer>(&self, mut serializer: S) {
|
||||
// Record that this node needs fixing on JS side
|
||||
serializer.record_fix_path();
|
||||
Null(()).serialize(serializer);
|
||||
}
|
||||
}
|
||||
@@ -431,7 +463,9 @@ impl ESTree for BigIntLiteralValue<'_, '_> {
|
||||
pub struct RegExpLiteralValue<'a, 'b>(#[expect(dead_code)] pub &'b RegExpLiteral<'a>);
|
||||
|
||||
impl ESTree for RegExpLiteralValue<'_, '_> {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) {
|
||||
fn serialize<S: Serializer>(&self, mut serializer: S) {
|
||||
// Record that this node needs fixing on JS side
|
||||
serializer.record_fix_path();
|
||||
Null(()).serialize(serializer);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,7 +19,7 @@ workspace = true
|
||||
doctest = false
|
||||
|
||||
[dependencies]
|
||||
oxc_data_structures = { workspace = true, features = ["code_buffer"], optional = true }
|
||||
oxc_data_structures = { workspace = true, features = ["code_buffer", "stack"], optional = true }
|
||||
|
||||
itoa = { workspace = true, optional = true }
|
||||
ryu-js = { workspace = true, optional = true }
|
||||
|
||||
@@ -2,6 +2,8 @@
|
||||
pub trait Config {
|
||||
/// `true` if output should contain TS fields
|
||||
const INCLUDE_TS_FIELDS: bool;
|
||||
/// `true` if should record paths to `Literal` nodes that need fixing on JS side
|
||||
const FIXES: bool;
|
||||
|
||||
fn new() -> Self;
|
||||
}
|
||||
@@ -11,6 +13,7 @@ pub struct ConfigTS;
|
||||
|
||||
impl Config for ConfigTS {
|
||||
const INCLUDE_TS_FIELDS: bool = true;
|
||||
const FIXES: bool = false;
|
||||
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
@@ -23,6 +26,33 @@ pub struct ConfigJS;
|
||||
|
||||
impl Config for ConfigJS {
|
||||
const INCLUDE_TS_FIELDS: bool = false;
|
||||
const FIXES: bool = false;
|
||||
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for serializing AST with TypeScript fields, with fixes.
|
||||
pub struct ConfigFixesTS;
|
||||
|
||||
impl Config for ConfigFixesTS {
|
||||
const INCLUDE_TS_FIELDS: bool = true;
|
||||
const FIXES: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
Self
|
||||
}
|
||||
}
|
||||
|
||||
/// Config for serializing AST without TypeScript fields, with fixes.
|
||||
pub struct ConfigFixesJS;
|
||||
|
||||
impl Config for ConfigFixesJS {
|
||||
const INCLUDE_TS_FIELDS: bool = false;
|
||||
const FIXES: bool = true;
|
||||
|
||||
#[inline(always)]
|
||||
fn new() -> Self {
|
||||
|
||||
@@ -1,7 +1,11 @@
|
||||
// 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;
|
||||
use std::mem;
|
||||
|
||||
use itoa::Buffer as ItoaBuffer;
|
||||
|
||||
use oxc_data_structures::{code_buffer::CodeBuffer, stack::NonEmptyStack};
|
||||
|
||||
mod blanket;
|
||||
mod config;
|
||||
@@ -10,7 +14,7 @@ mod primitives;
|
||||
mod sequences;
|
||||
mod strings;
|
||||
mod structs;
|
||||
use config::{Config, ConfigJS, ConfigTS};
|
||||
use config::{Config, ConfigFixesJS, ConfigFixesTS, ConfigJS, ConfigTS};
|
||||
use formatter::{CompactFormatter, Formatter, PrettyFormatter};
|
||||
use sequences::ESTreeSequenceSerializer;
|
||||
use structs::ESTreeStructSerializer;
|
||||
@@ -43,6 +47,13 @@ pub trait Serializer: SerializerPrivate {
|
||||
|
||||
/// Serialize sequence.
|
||||
fn serialize_sequence(self) -> Self::SequenceSerializer;
|
||||
|
||||
/// Record path to current node in `fixes_buffer`.
|
||||
///
|
||||
/// Used by serializers for the `value` field of `BigIntLiteral` and `RegExpLiteral`.
|
||||
/// 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.
|
||||
@@ -69,10 +80,24 @@ pub type PrettyTSSerializer = ESTreeSerializer<ConfigTS, PrettyFormatter>;
|
||||
/// ESTree serializer which produces pretty JSON, excluding TypeScript fields.
|
||||
pub type PrettyJSSerializer = ESTreeSerializer<ConfigJS, PrettyFormatter>;
|
||||
|
||||
/// ESTree serializer which produces compact JSON, including TypeScript fields.
|
||||
pub type CompactFixesTSSerializer = ESTreeSerializer<ConfigFixesTS, CompactFormatter>;
|
||||
|
||||
/// ESTree serializer which produces compact JSON, excluding TypeScript fields.
|
||||
pub type CompactFixesJSSerializer = ESTreeSerializer<ConfigFixesJS, CompactFormatter>;
|
||||
|
||||
/// ESTree serializer which produces pretty JSON, including TypeScript fields.
|
||||
pub type PrettyFixesTSSerializer = ESTreeSerializer<ConfigFixesTS, PrettyFormatter>;
|
||||
|
||||
/// ESTree serializer which produces pretty JSON, excluding TypeScript fields.
|
||||
pub type PrettyFixesJSSerializer = ESTreeSerializer<ConfigFixesJS, PrettyFormatter>;
|
||||
|
||||
/// ESTree serializer.
|
||||
pub struct ESTreeSerializer<C: Config, F: Formatter> {
|
||||
buffer: CodeBuffer,
|
||||
formatter: F,
|
||||
trace_path: NonEmptyStack<TracePathPart>,
|
||||
fixes_buffer: CodeBuffer,
|
||||
#[expect(unused)]
|
||||
config: C,
|
||||
}
|
||||
@@ -80,12 +105,56 @@ pub struct ESTreeSerializer<C: Config, F: Formatter> {
|
||||
impl<C: Config, F: Formatter> ESTreeSerializer<C, F> {
|
||||
/// Create new [`ESTreeSerializer`].
|
||||
pub fn new() -> Self {
|
||||
Self { buffer: CodeBuffer::new(), formatter: F::new(), config: C::new() }
|
||||
Self {
|
||||
buffer: CodeBuffer::new(),
|
||||
formatter: F::new(),
|
||||
trace_path: NonEmptyStack::new(TracePathPart::Index(0)),
|
||||
fixes_buffer: CodeBuffer::new(),
|
||||
config: C::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create new [`ESTreeSerializer`] with specified buffer capacity.
|
||||
pub fn with_capacity(capacity: usize) -> Self {
|
||||
Self { buffer: CodeBuffer::with_capacity(capacity), formatter: F::new(), config: C::new() }
|
||||
Self {
|
||||
buffer: CodeBuffer::with_capacity(capacity),
|
||||
formatter: F::new(),
|
||||
trace_path: NonEmptyStack::new(TracePathPart::Index(0)),
|
||||
fixes_buffer: CodeBuffer::new(),
|
||||
config: C::new(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Serialize `node` and output a `JSON` string containing
|
||||
/// `{ "node": { ... }, "fixes": [ ... ]}`, where `node` is the serialized AST node,
|
||||
/// and `fixes` is a list of paths to any `Literal`s which are `BigInt`s or `RegExp`s.
|
||||
///
|
||||
/// The `value` field of these nodes cannot be serialized to JSON, because JSON doesn't support
|
||||
/// `BigInt`s or `RegExp`s. The `fixes` paths can be used on JS side to locate these nodes
|
||||
/// and set their `value` fields correctly.
|
||||
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(r#"{"node":"#);
|
||||
|
||||
node.serialize(&mut self);
|
||||
|
||||
debug_assert_eq!(self.trace_path.len(), 1);
|
||||
debug_assert_eq!(self.trace_path[0], TracePathPart::DUMMY);
|
||||
|
||||
self.buffer.print_str(r#","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()
|
||||
}
|
||||
|
||||
/// Consume this [`ESTreeSerializer`] and convert buffer to string.
|
||||
@@ -119,6 +188,42 @@ impl<'s, C: Config, F: Formatter> Serializer for &'s mut ESTreeSerializer<C, F>
|
||||
fn serialize_sequence(self) -> ESTreeSequenceSerializer<'s, C, F> {
|
||||
ESTreeSequenceSerializer::new(self)
|
||||
}
|
||||
|
||||
/// Record path to current node in `fixes_buffer`.
|
||||
///
|
||||
/// Used by serializers for the `value` field of `BigIntLiteral` and `RegExpLiteral`.
|
||||
/// 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) {
|
||||
if !C::FIXES {
|
||||
return;
|
||||
}
|
||||
|
||||
self.fixes_buffer.print_str(",[");
|
||||
|
||||
// First part is a dummy, last part is `"value"`, so skip them
|
||||
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_ascii_byte(b'"');
|
||||
self.fixes_buffer.print_str(key);
|
||||
self.fixes_buffer.print_ascii_byte(b'"');
|
||||
}
|
||||
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']');
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Config, F: Formatter> SerializerPrivate for &mut ESTreeSerializer<C, F> {
|
||||
@@ -136,3 +241,14 @@ impl<C: Config, F: Formatter> SerializerPrivate for &mut ESTreeSerializer<C, F>
|
||||
(&mut self.buffer, &mut self.formatter)
|
||||
}
|
||||
}
|
||||
|
||||
/// Element of a trace path.
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
pub enum TracePathPart {
|
||||
Key(&'static str),
|
||||
Index(usize),
|
||||
}
|
||||
|
||||
impl TracePathPart {
|
||||
pub const DUMMY: Self = TracePathPart::Index(0);
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
use super::{Config, ESTree, ESTreeSerializer, Formatter, Serializer, SerializerPrivate};
|
||||
use super::{
|
||||
Config, ESTree, ESTreeSerializer, Formatter, Serializer, SerializerPrivate, TracePathPart,
|
||||
};
|
||||
|
||||
/// Trait for sequence serializers.
|
||||
pub trait SequenceSerializer {
|
||||
@@ -15,25 +17,35 @@ pub trait SequenceSerializer {
|
||||
pub struct ESTreeSequenceSerializer<'s, C: Config, F: Formatter> {
|
||||
/// Serializer
|
||||
serializer: &'s mut ESTreeSerializer<C, F>,
|
||||
/// State of sequence.
|
||||
/// Starts as `SequenceState::Empty`, transitions to `SequenceState::HasEntries` on first entry.
|
||||
state: SequenceState,
|
||||
/// Length of sequence
|
||||
len: usize,
|
||||
}
|
||||
|
||||
impl<'s, C: Config, F: Formatter> ESTreeSequenceSerializer<'s, C, F> {
|
||||
/// Create new [`ESTreeSequenceSerializer`].
|
||||
pub(super) fn new(mut serializer: &'s mut ESTreeSerializer<C, F>) -> Self {
|
||||
// Push item to `trace_path`. It will be replaced with a `TracePathPart::Index`
|
||||
// when serializing each item in the sequence, and popped off again in `end` method.
|
||||
if C::FIXES {
|
||||
serializer.trace_path.push(TracePathPart::DUMMY);
|
||||
}
|
||||
|
||||
serializer.buffer_mut().print_ascii_byte(b'[');
|
||||
Self { serializer, state: SequenceState::Empty }
|
||||
|
||||
Self { serializer, len: 0 }
|
||||
}
|
||||
}
|
||||
|
||||
impl<C: Config, F: Formatter> SequenceSerializer for ESTreeSequenceSerializer<'_, C, F> {
|
||||
/// Serialize sequence entry.
|
||||
fn serialize_element<T: ESTree + ?Sized>(&mut self, value: &T) {
|
||||
// Update last item in trace path to current sequence index
|
||||
if C::FIXES {
|
||||
*self.serializer.trace_path.last_mut() = TracePathPart::Index(self.len);
|
||||
}
|
||||
|
||||
let (buffer, formatter) = self.serializer.buffer_and_formatter_mut();
|
||||
if self.state == SequenceState::Empty {
|
||||
self.state = SequenceState::HasEntries;
|
||||
if self.len == 0 {
|
||||
formatter.before_first_element(buffer);
|
||||
} else {
|
||||
buffer.print_ascii_byte(b',');
|
||||
@@ -41,25 +53,27 @@ impl<C: Config, F: Formatter> SequenceSerializer for ESTreeSequenceSerializer<'_
|
||||
}
|
||||
|
||||
value.serialize(&mut *self.serializer);
|
||||
self.len += 1;
|
||||
}
|
||||
|
||||
/// Finish serializing sequence.
|
||||
fn end(mut self) {
|
||||
// Pop entry for this sequence from `trace_path`
|
||||
if C::FIXES {
|
||||
// SAFETY: `trace_path` is pushed to in `new`, which is only way to create an `ESTreeSequenceSerializer`.
|
||||
// This method consumes the `ESTreeSequenceSerializer`, so this method can't be called more
|
||||
// times than `new`. So there must be an item to pop.
|
||||
unsafe { self.serializer.trace_path.pop_unchecked() };
|
||||
}
|
||||
|
||||
let (buffer, formatter) = self.serializer.buffer_and_formatter_mut();
|
||||
if self.state == SequenceState::HasEntries {
|
||||
if self.len > 0 {
|
||||
formatter.after_last_element(buffer);
|
||||
}
|
||||
buffer.print_ascii_byte(b']');
|
||||
}
|
||||
}
|
||||
|
||||
/// State of [`ESTreeSequenceSerializer`].
|
||||
#[derive(Clone, Copy, PartialEq, Eq)]
|
||||
enum SequenceState {
|
||||
Empty,
|
||||
HasEntries,
|
||||
}
|
||||
|
||||
/// [`ESTree`] implementation for slices.
|
||||
impl<T: ESTree> ESTree for &[T] {
|
||||
fn serialize<S: Serializer>(&self, serializer: S) {
|
||||
|
||||
@@ -2,7 +2,7 @@ use oxc_data_structures::code_buffer::CodeBuffer;
|
||||
|
||||
use super::{
|
||||
Config, ESTree, ESTreeSequenceSerializer, ESTreeSerializer, Formatter, Serializer,
|
||||
SerializerPrivate,
|
||||
SerializerPrivate, TracePathPart,
|
||||
};
|
||||
|
||||
/// Trait for struct serializers.
|
||||
@@ -13,7 +13,7 @@ pub trait StructSerializer {
|
||||
/// Serialize struct field.
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T);
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T);
|
||||
|
||||
/// Serialize struct field which is TypeScript syntax.
|
||||
///
|
||||
@@ -24,7 +24,7 @@ pub trait StructSerializer {
|
||||
/// i.e. the field is skipped.
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T);
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T);
|
||||
|
||||
/// Finish serializing struct.
|
||||
fn end(self);
|
||||
@@ -44,7 +44,14 @@ 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<C, F>) -> Self {
|
||||
// Push item to `trace_path`. It will be replaced with a `TracePathPart::Key`
|
||||
// when serializing each field in the struct, and popped off again in `end` method.
|
||||
if C::FIXES {
|
||||
serializer.trace_path.push(TracePathPart::DUMMY);
|
||||
}
|
||||
|
||||
serializer.buffer_mut().print_ascii_byte(b'{');
|
||||
|
||||
Self { serializer, state: StructState::Empty }
|
||||
}
|
||||
}
|
||||
@@ -56,7 +63,12 @@ impl<C: Config, F: Formatter> StructSerializer for ESTreeStructSerializer<'_, C,
|
||||
/// Serialize struct field.
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T) {
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T) {
|
||||
// Update last item in trace path to current key
|
||||
if C::FIXES {
|
||||
*self.serializer.trace_path.last_mut() = TracePathPart::Key(key);
|
||||
}
|
||||
|
||||
let (buffer, formatter) = self.serializer.buffer_and_formatter_mut();
|
||||
if self.state == StructState::Empty {
|
||||
self.state = StructState::HasFields;
|
||||
@@ -83,7 +95,7 @@ impl<C: Config, F: Formatter> StructSerializer for ESTreeStructSerializer<'_, C,
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
#[inline(always)]
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T) {
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T) {
|
||||
if C::INCLUDE_TS_FIELDS {
|
||||
self.serialize_field(key, value);
|
||||
}
|
||||
@@ -92,6 +104,15 @@ impl<C: Config, F: Formatter> StructSerializer for ESTreeStructSerializer<'_, C,
|
||||
/// Finish serializing struct.
|
||||
fn end(self) {
|
||||
let mut serializer = self.serializer;
|
||||
|
||||
// Pop entry for this struct from `trace_path`
|
||||
if C::FIXES {
|
||||
// SAFETY: `trace_path` is pushed to in `new`, which is only way to create an `ESTreeStructSerializer`.
|
||||
// This method consumes the `ESTreeStructSerializer`, so this method can't be called more
|
||||
// times than `new`. So there must be an item to pop.
|
||||
unsafe { serializer.trace_path.pop_unchecked() };
|
||||
}
|
||||
|
||||
let (buffer, formatter) = serializer.buffer_and_formatter_mut();
|
||||
if self.state == StructState::HasFields {
|
||||
formatter.after_last_element(buffer);
|
||||
@@ -161,6 +182,12 @@ impl<'p, P: StructSerializer> Serializer for FlatStructSerializer<'p, P> {
|
||||
panic!("Cannot flatten a sequence into a struct");
|
||||
}
|
||||
}
|
||||
|
||||
fn record_fix_path(&mut self) {
|
||||
const {
|
||||
panic!("Cannot call `record_fix_path` on a `FlatStructSerializer`");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<P: StructSerializer> SerializerPrivate for FlatStructSerializer<'_, P> {
|
||||
@@ -187,7 +214,7 @@ impl<P: StructSerializer> StructSerializer for FlatStructSerializer<'_, P> {
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
#[inline(always)]
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T) {
|
||||
fn serialize_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T) {
|
||||
// Delegate to parent `StructSerializer`
|
||||
self.0.serialize_field(key, value);
|
||||
}
|
||||
@@ -202,7 +229,7 @@ impl<P: StructSerializer> StructSerializer for FlatStructSerializer<'_, P> {
|
||||
///
|
||||
/// `key` must not contain any characters which require escaping in JSON.
|
||||
#[inline(always)]
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &str, value: &T) {
|
||||
fn serialize_ts_field<T: ESTree + ?Sized>(&mut self, key: &'static str, value: &T) {
|
||||
// Delegate to parent `StructSerializer`
|
||||
self.0.serialize_ts_field(key, value);
|
||||
}
|
||||
|
||||
@@ -1,29 +0,0 @@
|
||||
import { visitorKeys as visitorKeysOriginal } from '@typescript-eslint/visitor-keys';
|
||||
import { writeFileSync } from 'node:fs';
|
||||
import { join as pathJoin } from 'node:path';
|
||||
|
||||
const PATH_CJS = pathJoin(import.meta.dirname, 'generated/visitor-keys.cjs');
|
||||
const PATH_MJS = pathJoin(import.meta.dirname, 'generated/visitor-keys.mjs');
|
||||
|
||||
// Add keys for `ParenthesizedExpression` and `TSParenthesizedType`, which TS-ESLint doesn't have
|
||||
const visitorKeys = {
|
||||
...visitorKeysOriginal,
|
||||
ParenthesizedExpression: ['expression'],
|
||||
TSParenthesizedType: ['typeAnnotation'],
|
||||
};
|
||||
|
||||
const keys = Object.entries(visitorKeys)
|
||||
.filter(([, v]) => v?.length)
|
||||
.map(([k, v]) => ` ${k}: [${v.map((v) => `'${v}'`).join(', ')}],`)
|
||||
.join('\n');
|
||||
|
||||
const code = `// Auto-generated code, DO NOT EDIT DIRECTLY!
|
||||
// To edit this generated file you have to edit \`napi/parser/generate-visitor-keys.mjs\`.
|
||||
|
||||
const visitorKeys = {
|
||||
${keys}
|
||||
};
|
||||
`;
|
||||
|
||||
writeFileSync(PATH_CJS, `${code}module.exports = visitorKeys;\n`);
|
||||
writeFileSync(PATH_MJS, `${code}export default visitorKeys;\n`);
|
||||
@@ -1,143 +0,0 @@
|
||||
// Auto-generated code, DO NOT EDIT DIRECTLY!
|
||||
// To edit this generated file you have to edit `napi/parser/generate-visitor-keys.mjs`.
|
||||
|
||||
const visitorKeys = {
|
||||
ArrayExpression: ['elements'],
|
||||
ArrayPattern: ['decorators', 'elements', 'typeAnnotation'],
|
||||
ArrowFunctionExpression: ['typeParameters', 'params', 'returnType', 'body'],
|
||||
AssignmentExpression: ['left', 'right'],
|
||||
AssignmentPattern: ['decorators', 'left', 'right', 'typeAnnotation'],
|
||||
AwaitExpression: ['argument'],
|
||||
BinaryExpression: ['left', 'right'],
|
||||
BlockStatement: ['body'],
|
||||
BreakStatement: ['label'],
|
||||
CallExpression: ['callee', 'typeArguments', 'arguments'],
|
||||
CatchClause: ['param', 'body'],
|
||||
ChainExpression: ['expression'],
|
||||
ClassBody: ['body'],
|
||||
ClassDeclaration: ['decorators', 'id', 'typeParameters', 'superClass', 'superTypeArguments', 'implements', 'body'],
|
||||
ClassExpression: ['decorators', 'id', 'typeParameters', 'superClass', 'superTypeArguments', 'implements', 'body'],
|
||||
ConditionalExpression: ['test', 'consequent', 'alternate'],
|
||||
ContinueStatement: ['label'],
|
||||
DoWhileStatement: ['body', 'test'],
|
||||
ExperimentalRestProperty: ['argument'],
|
||||
ExperimentalSpreadProperty: ['argument'],
|
||||
ExportAllDeclaration: ['exported', 'source', 'attributes'],
|
||||
ExportDefaultDeclaration: ['declaration'],
|
||||
ExportNamedDeclaration: ['declaration', 'specifiers', 'source', 'attributes'],
|
||||
ExportSpecifier: ['exported', 'local'],
|
||||
ExpressionStatement: ['expression'],
|
||||
ForInStatement: ['left', 'right', 'body'],
|
||||
ForOfStatement: ['left', 'right', 'body'],
|
||||
ForStatement: ['init', 'test', 'update', 'body'],
|
||||
FunctionDeclaration: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
FunctionExpression: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
Identifier: ['decorators', 'typeAnnotation'],
|
||||
IfStatement: ['test', 'consequent', 'alternate'],
|
||||
ImportAttribute: ['key', 'value'],
|
||||
ImportDeclaration: ['specifiers', 'source', 'attributes'],
|
||||
ImportDefaultSpecifier: ['local'],
|
||||
ImportExpression: ['source', 'options'],
|
||||
ImportNamespaceSpecifier: ['local'],
|
||||
ImportSpecifier: ['imported', 'local'],
|
||||
JSXAttribute: ['name', 'value'],
|
||||
JSXClosingElement: ['name'],
|
||||
JSXElement: ['openingElement', 'children', 'closingElement'],
|
||||
JSXExpressionContainer: ['expression'],
|
||||
JSXFragment: ['openingFragment', 'children', 'closingFragment'],
|
||||
JSXMemberExpression: ['object', 'property'],
|
||||
JSXNamespacedName: ['namespace', 'name'],
|
||||
JSXOpeningElement: ['name', 'typeArguments', 'attributes'],
|
||||
JSXSpreadAttribute: ['argument'],
|
||||
JSXSpreadChild: ['expression'],
|
||||
LabeledStatement: ['label', 'body'],
|
||||
LogicalExpression: ['left', 'right'],
|
||||
MemberExpression: ['object', 'property'],
|
||||
MetaProperty: ['meta', 'property'],
|
||||
MethodDefinition: ['decorators', 'key', 'value'],
|
||||
NewExpression: ['callee', 'typeArguments', 'arguments'],
|
||||
ObjectExpression: ['properties'],
|
||||
ObjectPattern: ['decorators', 'properties', 'typeAnnotation'],
|
||||
Program: ['body'],
|
||||
Property: ['key', 'value'],
|
||||
PropertyDefinition: ['decorators', 'key', 'typeAnnotation', 'value'],
|
||||
RestElement: ['decorators', 'argument', 'typeAnnotation'],
|
||||
ReturnStatement: ['argument'],
|
||||
SequenceExpression: ['expressions'],
|
||||
SpreadElement: ['argument'],
|
||||
StaticBlock: ['body'],
|
||||
SwitchCase: ['test', 'consequent'],
|
||||
SwitchStatement: ['discriminant', 'cases'],
|
||||
TaggedTemplateExpression: ['tag', 'typeArguments', 'quasi'],
|
||||
TemplateLiteral: ['quasis', 'expressions'],
|
||||
ThrowStatement: ['argument'],
|
||||
TryStatement: ['block', 'handler', 'finalizer'],
|
||||
UnaryExpression: ['argument'],
|
||||
UpdateExpression: ['argument'],
|
||||
VariableDeclaration: ['declarations'],
|
||||
VariableDeclarator: ['id', 'init'],
|
||||
WhileStatement: ['test', 'body'],
|
||||
WithStatement: ['object', 'body'],
|
||||
YieldExpression: ['argument'],
|
||||
AccessorProperty: ['decorators', 'key', 'typeAnnotation', 'value'],
|
||||
Decorator: ['expression'],
|
||||
TSAbstractAccessorProperty: ['decorators', 'key', 'typeAnnotation'],
|
||||
TSAbstractMethodDefinition: ['key', 'value'],
|
||||
TSAbstractPropertyDefinition: ['decorators', 'key', 'typeAnnotation'],
|
||||
TSArrayType: ['elementType'],
|
||||
TSAsExpression: ['expression', 'typeAnnotation'],
|
||||
TSCallSignatureDeclaration: ['typeParameters', 'params', 'returnType'],
|
||||
TSClassImplements: ['expression', 'typeArguments'],
|
||||
TSConditionalType: ['checkType', 'extendsType', 'trueType', 'falseType'],
|
||||
TSConstructorType: ['typeParameters', 'params', 'returnType'],
|
||||
TSConstructSignatureDeclaration: ['typeParameters', 'params', 'returnType'],
|
||||
TSDeclareFunction: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
TSEmptyBodyFunctionExpression: ['id', 'typeParameters', 'params', 'returnType'],
|
||||
TSEnumBody: ['members'],
|
||||
TSEnumDeclaration: ['id', 'body'],
|
||||
TSEnumMember: ['id', 'initializer'],
|
||||
TSExportAssignment: ['expression'],
|
||||
TSExternalModuleReference: ['expression'],
|
||||
TSFunctionType: ['typeParameters', 'params', 'returnType'],
|
||||
TSImportEqualsDeclaration: ['id', 'moduleReference'],
|
||||
TSImportType: ['argument', 'qualifier', 'typeArguments', 'options'],
|
||||
TSIndexedAccessType: ['indexType', 'objectType'],
|
||||
TSIndexSignature: ['parameters', 'typeAnnotation'],
|
||||
TSInferType: ['typeParameter'],
|
||||
TSInstantiationExpression: ['expression', 'typeArguments'],
|
||||
TSInterfaceBody: ['body'],
|
||||
TSInterfaceDeclaration: ['id', 'typeParameters', 'extends', 'body'],
|
||||
TSInterfaceHeritage: ['expression', 'typeArguments'],
|
||||
TSIntersectionType: ['types'],
|
||||
TSLiteralType: ['literal'],
|
||||
TSMappedType: ['key', 'constraint', 'nameType', 'typeAnnotation'],
|
||||
TSMethodSignature: ['typeParameters', 'key', 'params', 'returnType'],
|
||||
TSModuleBlock: ['body'],
|
||||
TSModuleDeclaration: ['id', 'body'],
|
||||
TSNamedTupleMember: ['label', 'elementType'],
|
||||
TSNamespaceExportDeclaration: ['id'],
|
||||
TSNonNullExpression: ['expression'],
|
||||
TSOptionalType: ['typeAnnotation'],
|
||||
TSParameterProperty: ['decorators', 'parameter'],
|
||||
TSPropertySignature: ['typeAnnotation', 'key'],
|
||||
TSQualifiedName: ['left', 'right'],
|
||||
TSRestType: ['typeAnnotation'],
|
||||
TSSatisfiesExpression: ['expression', 'typeAnnotation'],
|
||||
TSTemplateLiteralType: ['quasis', 'types'],
|
||||
TSTupleType: ['elementTypes'],
|
||||
TSTypeAliasDeclaration: ['id', 'typeParameters', 'typeAnnotation'],
|
||||
TSTypeAnnotation: ['typeAnnotation'],
|
||||
TSTypeAssertion: ['typeAnnotation', 'expression'],
|
||||
TSTypeLiteral: ['members'],
|
||||
TSTypeOperator: ['typeAnnotation'],
|
||||
TSTypeParameter: ['name', 'constraint', 'default'],
|
||||
TSTypeParameterDeclaration: ['params'],
|
||||
TSTypeParameterInstantiation: ['params'],
|
||||
TSTypePredicate: ['typeAnnotation', 'parameterName'],
|
||||
TSTypeQuery: ['exprName', 'typeArguments'],
|
||||
TSTypeReference: ['typeName', 'typeArguments'],
|
||||
TSUnionType: ['types'],
|
||||
ParenthesizedExpression: ['expression'],
|
||||
TSParenthesizedType: ['typeAnnotation'],
|
||||
};
|
||||
module.exports = visitorKeys;
|
||||
@@ -1,143 +0,0 @@
|
||||
// Auto-generated code, DO NOT EDIT DIRECTLY!
|
||||
// To edit this generated file you have to edit `napi/parser/generate-visitor-keys.mjs`.
|
||||
|
||||
const visitorKeys = {
|
||||
ArrayExpression: ['elements'],
|
||||
ArrayPattern: ['decorators', 'elements', 'typeAnnotation'],
|
||||
ArrowFunctionExpression: ['typeParameters', 'params', 'returnType', 'body'],
|
||||
AssignmentExpression: ['left', 'right'],
|
||||
AssignmentPattern: ['decorators', 'left', 'right', 'typeAnnotation'],
|
||||
AwaitExpression: ['argument'],
|
||||
BinaryExpression: ['left', 'right'],
|
||||
BlockStatement: ['body'],
|
||||
BreakStatement: ['label'],
|
||||
CallExpression: ['callee', 'typeArguments', 'arguments'],
|
||||
CatchClause: ['param', 'body'],
|
||||
ChainExpression: ['expression'],
|
||||
ClassBody: ['body'],
|
||||
ClassDeclaration: ['decorators', 'id', 'typeParameters', 'superClass', 'superTypeArguments', 'implements', 'body'],
|
||||
ClassExpression: ['decorators', 'id', 'typeParameters', 'superClass', 'superTypeArguments', 'implements', 'body'],
|
||||
ConditionalExpression: ['test', 'consequent', 'alternate'],
|
||||
ContinueStatement: ['label'],
|
||||
DoWhileStatement: ['body', 'test'],
|
||||
ExperimentalRestProperty: ['argument'],
|
||||
ExperimentalSpreadProperty: ['argument'],
|
||||
ExportAllDeclaration: ['exported', 'source', 'attributes'],
|
||||
ExportDefaultDeclaration: ['declaration'],
|
||||
ExportNamedDeclaration: ['declaration', 'specifiers', 'source', 'attributes'],
|
||||
ExportSpecifier: ['exported', 'local'],
|
||||
ExpressionStatement: ['expression'],
|
||||
ForInStatement: ['left', 'right', 'body'],
|
||||
ForOfStatement: ['left', 'right', 'body'],
|
||||
ForStatement: ['init', 'test', 'update', 'body'],
|
||||
FunctionDeclaration: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
FunctionExpression: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
Identifier: ['decorators', 'typeAnnotation'],
|
||||
IfStatement: ['test', 'consequent', 'alternate'],
|
||||
ImportAttribute: ['key', 'value'],
|
||||
ImportDeclaration: ['specifiers', 'source', 'attributes'],
|
||||
ImportDefaultSpecifier: ['local'],
|
||||
ImportExpression: ['source', 'options'],
|
||||
ImportNamespaceSpecifier: ['local'],
|
||||
ImportSpecifier: ['imported', 'local'],
|
||||
JSXAttribute: ['name', 'value'],
|
||||
JSXClosingElement: ['name'],
|
||||
JSXElement: ['openingElement', 'children', 'closingElement'],
|
||||
JSXExpressionContainer: ['expression'],
|
||||
JSXFragment: ['openingFragment', 'children', 'closingFragment'],
|
||||
JSXMemberExpression: ['object', 'property'],
|
||||
JSXNamespacedName: ['namespace', 'name'],
|
||||
JSXOpeningElement: ['name', 'typeArguments', 'attributes'],
|
||||
JSXSpreadAttribute: ['argument'],
|
||||
JSXSpreadChild: ['expression'],
|
||||
LabeledStatement: ['label', 'body'],
|
||||
LogicalExpression: ['left', 'right'],
|
||||
MemberExpression: ['object', 'property'],
|
||||
MetaProperty: ['meta', 'property'],
|
||||
MethodDefinition: ['decorators', 'key', 'value'],
|
||||
NewExpression: ['callee', 'typeArguments', 'arguments'],
|
||||
ObjectExpression: ['properties'],
|
||||
ObjectPattern: ['decorators', 'properties', 'typeAnnotation'],
|
||||
Program: ['body'],
|
||||
Property: ['key', 'value'],
|
||||
PropertyDefinition: ['decorators', 'key', 'typeAnnotation', 'value'],
|
||||
RestElement: ['decorators', 'argument', 'typeAnnotation'],
|
||||
ReturnStatement: ['argument'],
|
||||
SequenceExpression: ['expressions'],
|
||||
SpreadElement: ['argument'],
|
||||
StaticBlock: ['body'],
|
||||
SwitchCase: ['test', 'consequent'],
|
||||
SwitchStatement: ['discriminant', 'cases'],
|
||||
TaggedTemplateExpression: ['tag', 'typeArguments', 'quasi'],
|
||||
TemplateLiteral: ['quasis', 'expressions'],
|
||||
ThrowStatement: ['argument'],
|
||||
TryStatement: ['block', 'handler', 'finalizer'],
|
||||
UnaryExpression: ['argument'],
|
||||
UpdateExpression: ['argument'],
|
||||
VariableDeclaration: ['declarations'],
|
||||
VariableDeclarator: ['id', 'init'],
|
||||
WhileStatement: ['test', 'body'],
|
||||
WithStatement: ['object', 'body'],
|
||||
YieldExpression: ['argument'],
|
||||
AccessorProperty: ['decorators', 'key', 'typeAnnotation', 'value'],
|
||||
Decorator: ['expression'],
|
||||
TSAbstractAccessorProperty: ['decorators', 'key', 'typeAnnotation'],
|
||||
TSAbstractMethodDefinition: ['key', 'value'],
|
||||
TSAbstractPropertyDefinition: ['decorators', 'key', 'typeAnnotation'],
|
||||
TSArrayType: ['elementType'],
|
||||
TSAsExpression: ['expression', 'typeAnnotation'],
|
||||
TSCallSignatureDeclaration: ['typeParameters', 'params', 'returnType'],
|
||||
TSClassImplements: ['expression', 'typeArguments'],
|
||||
TSConditionalType: ['checkType', 'extendsType', 'trueType', 'falseType'],
|
||||
TSConstructorType: ['typeParameters', 'params', 'returnType'],
|
||||
TSConstructSignatureDeclaration: ['typeParameters', 'params', 'returnType'],
|
||||
TSDeclareFunction: ['id', 'typeParameters', 'params', 'returnType', 'body'],
|
||||
TSEmptyBodyFunctionExpression: ['id', 'typeParameters', 'params', 'returnType'],
|
||||
TSEnumBody: ['members'],
|
||||
TSEnumDeclaration: ['id', 'body'],
|
||||
TSEnumMember: ['id', 'initializer'],
|
||||
TSExportAssignment: ['expression'],
|
||||
TSExternalModuleReference: ['expression'],
|
||||
TSFunctionType: ['typeParameters', 'params', 'returnType'],
|
||||
TSImportEqualsDeclaration: ['id', 'moduleReference'],
|
||||
TSImportType: ['argument', 'qualifier', 'typeArguments', 'options'],
|
||||
TSIndexedAccessType: ['indexType', 'objectType'],
|
||||
TSIndexSignature: ['parameters', 'typeAnnotation'],
|
||||
TSInferType: ['typeParameter'],
|
||||
TSInstantiationExpression: ['expression', 'typeArguments'],
|
||||
TSInterfaceBody: ['body'],
|
||||
TSInterfaceDeclaration: ['id', 'typeParameters', 'extends', 'body'],
|
||||
TSInterfaceHeritage: ['expression', 'typeArguments'],
|
||||
TSIntersectionType: ['types'],
|
||||
TSLiteralType: ['literal'],
|
||||
TSMappedType: ['key', 'constraint', 'nameType', 'typeAnnotation'],
|
||||
TSMethodSignature: ['typeParameters', 'key', 'params', 'returnType'],
|
||||
TSModuleBlock: ['body'],
|
||||
TSModuleDeclaration: ['id', 'body'],
|
||||
TSNamedTupleMember: ['label', 'elementType'],
|
||||
TSNamespaceExportDeclaration: ['id'],
|
||||
TSNonNullExpression: ['expression'],
|
||||
TSOptionalType: ['typeAnnotation'],
|
||||
TSParameterProperty: ['decorators', 'parameter'],
|
||||
TSPropertySignature: ['typeAnnotation', 'key'],
|
||||
TSQualifiedName: ['left', 'right'],
|
||||
TSRestType: ['typeAnnotation'],
|
||||
TSSatisfiesExpression: ['expression', 'typeAnnotation'],
|
||||
TSTemplateLiteralType: ['quasis', 'types'],
|
||||
TSTupleType: ['elementTypes'],
|
||||
TSTypeAliasDeclaration: ['id', 'typeParameters', 'typeAnnotation'],
|
||||
TSTypeAnnotation: ['typeAnnotation'],
|
||||
TSTypeAssertion: ['typeAnnotation', 'expression'],
|
||||
TSTypeLiteral: ['members'],
|
||||
TSTypeOperator: ['typeAnnotation'],
|
||||
TSTypeParameter: ['name', 'constraint', 'default'],
|
||||
TSTypeParameterDeclaration: ['params'],
|
||||
TSTypeParameterInstantiation: ['params'],
|
||||
TSTypePredicate: ['typeAnnotation', 'parameterName'],
|
||||
TSTypeQuery: ['exprName', 'typeArguments'],
|
||||
TSTypeReference: ['typeName', 'typeArguments'],
|
||||
TSUnionType: ['types'],
|
||||
ParenthesizedExpression: ['expression'],
|
||||
TSParenthesizedType: ['typeAnnotation'],
|
||||
};
|
||||
export default visitorKeys;
|
||||
@@ -4,7 +4,7 @@
|
||||
"main": "index.js",
|
||||
"browser": "wasm.mjs",
|
||||
"scripts": {
|
||||
"build-dev": "node generate-visitor-keys.mjs && napi build --no-dts-cache --platform --js bindings.js",
|
||||
"build-dev": "napi build --no-dts-cache --platform --js bindings.js",
|
||||
"build": "pnpm run build-dev --features allocator --release",
|
||||
"postbuild-dev": "node patch.mjs",
|
||||
"build-wasi": "pnpm run build-dev --release --target wasm32-wasip1-threads",
|
||||
@@ -43,8 +43,6 @@
|
||||
"wasm.mjs",
|
||||
"bindings.js",
|
||||
"webcontainer-fallback.js",
|
||||
"generated/visitor-keys.cjs",
|
||||
"generated/visitor-keys.mjs",
|
||||
"generated/deserialize/js.js",
|
||||
"generated/deserialize/ts.js"
|
||||
],
|
||||
@@ -58,7 +56,6 @@
|
||||
"devDependencies": {
|
||||
"@codspeed/vitest-plugin": "^4.0.0",
|
||||
"@napi-rs/wasm-runtime": "^0.2.7",
|
||||
"@typescript-eslint/visitor-keys": "^8.31.1",
|
||||
"@vitest/browser": "3.1.2",
|
||||
"esbuild": "^0.25.0",
|
||||
"playwright": "^1.51.0",
|
||||
|
||||
@@ -103,7 +103,7 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption
|
||||
let mut comments =
|
||||
convert_utf8_to_utf16(&source_text, &mut program, &mut module_record, &mut errors);
|
||||
|
||||
let program = match ast_type {
|
||||
let program_and_fixes = match ast_type {
|
||||
AstType::JavaScript => {
|
||||
// Add hashbang to start of comments
|
||||
if let Some(hashbang) = &program.hashbang {
|
||||
@@ -118,20 +118,20 @@ fn parse_with_return(filename: &str, source_text: String, options: &ParserOption
|
||||
);
|
||||
}
|
||||
|
||||
program.to_estree_js_json()
|
||||
program.to_estree_js_json_with_fixes()
|
||||
}
|
||||
AstType::TypeScript => {
|
||||
// Note: `@typescript-eslint/parser` ignores hashbangs,
|
||||
// despite appearances to the contrary in AST explorers.
|
||||
// So we ignore them too.
|
||||
// See: https://github.com/typescript-eslint/typescript-eslint/issues/6500
|
||||
program.to_estree_ts_json()
|
||||
program.to_estree_ts_json_with_fixes()
|
||||
}
|
||||
};
|
||||
|
||||
let module = EcmaScriptModule::from(&module_record);
|
||||
|
||||
ParseResult { program, module, comments, errors }
|
||||
ParseResult { program_and_fixes, module, comments, errors }
|
||||
}
|
||||
|
||||
/// Parse synchronously.
|
||||
|
||||
@@ -41,7 +41,7 @@ pub struct ParserOptions {
|
||||
|
||||
#[napi]
|
||||
pub struct ParseResult {
|
||||
pub(crate) program: String,
|
||||
pub(crate) program_and_fixes: String,
|
||||
pub(crate) module: EcmaScriptModule,
|
||||
pub(crate) comments: Vec<Comment>,
|
||||
pub(crate) errors: Vec<OxcError>,
|
||||
@@ -51,7 +51,7 @@ pub struct ParseResult {
|
||||
impl ParseResult {
|
||||
#[napi(getter, ts_return_type = "import(\"@oxc-project/types\").Program")]
|
||||
pub fn get_program(&mut self) -> String {
|
||||
mem::take(&mut self.program)
|
||||
mem::take(&mut self.program_and_fixes)
|
||||
}
|
||||
|
||||
#[napi(getter)]
|
||||
|
||||
+18
-27
@@ -1,8 +1,6 @@
|
||||
// Note: This code is repeated in `wrap.mjs`.
|
||||
// Any changes should be applied in that file too.
|
||||
|
||||
const visitorKeys = require('./generated/visitor-keys.cjs');
|
||||
|
||||
module.exports.wrap = function wrap(result) {
|
||||
let program, module, comments, errors;
|
||||
return {
|
||||
@@ -25,39 +23,32 @@ module.exports.wrap = function wrap(result) {
|
||||
};
|
||||
};
|
||||
|
||||
// Set `value` field of `Literal`s which are `BigInt`s or `RegExp`s.
|
||||
//
|
||||
// Returned JSON contains an array `fixes` with paths to these nodes
|
||||
// e.g. for `123n; foo(/xyz/)`, `fixes` will be
|
||||
// `[["body", 0, "expression"], ["body", 1, "expression", "arguments", 2]]`.
|
||||
//
|
||||
// Walk down the AST to these nodes and alter them.
|
||||
// Compiling the list of fixes on Rust side avoids having to do a full AST traversal on JS side
|
||||
// to locate the likely very few `Literal`s which need fixing.
|
||||
function jsonParseAst(programJson) {
|
||||
const program = JSON.parse(programJson);
|
||||
transformNode(program);
|
||||
const { node: program, fixes } = JSON.parse(programJson);
|
||||
for (const fixPath of fixes) {
|
||||
applyFix(program, fixPath);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
function transformNode(node) {
|
||||
if (!node) return;
|
||||
if (Array.isArray(node)) {
|
||||
for (const el of node) {
|
||||
transformNode(el);
|
||||
}
|
||||
return;
|
||||
function applyFix(program, fixPath) {
|
||||
let node = program;
|
||||
for (const key of fixPath) {
|
||||
node = node[key];
|
||||
}
|
||||
|
||||
if (node.type === 'Literal') {
|
||||
transformLiteral(node);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = visitorKeys[node.type];
|
||||
if (!keys) return;
|
||||
for (const key of keys) {
|
||||
transformNode(node[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function transformLiteral(node) {
|
||||
// Set `value` field of `Literal`s for `BigInt`s and `RegExp`s.
|
||||
// This is not possible to do on Rust side, as neither can be represented correctly in JSON.
|
||||
if (node.bigint) {
|
||||
node.value = BigInt(node.bigint);
|
||||
} else if (node.regex) {
|
||||
} else {
|
||||
try {
|
||||
node.value = RegExp(node.regex.pattern, node.regex.flags);
|
||||
} catch (_err) {
|
||||
|
||||
+20
-28
@@ -1,8 +1,6 @@
|
||||
// Note: This code is repeated in `wrap.cjs`.
|
||||
// Any changes should be applied in that file too.
|
||||
|
||||
import visitorKeys from './generated/visitor-keys.mjs';
|
||||
|
||||
export function wrap(result) {
|
||||
let program, module, comments, errors;
|
||||
return {
|
||||
@@ -25,40 +23,34 @@ export function wrap(result) {
|
||||
};
|
||||
}
|
||||
|
||||
// Used by napi/playground/patch.mjs
|
||||
// Used by `napi/playground/patch.mjs`.
|
||||
//
|
||||
// Set `value` field of `Literal`s which are `BigInt`s or `RegExp`s.
|
||||
//
|
||||
// Returned JSON contains an array `fixes` with paths to these nodes
|
||||
// e.g. for `123n; foo(/xyz/)`, `fixes` will be
|
||||
// `[["body", 0, "expression"], ["body", 1, "expression", "arguments", 2]]`.
|
||||
//
|
||||
// Walk down the AST to these nodes and alter them.
|
||||
// Compiling the list of fixes on Rust side avoids having to do a full AST traversal on JS side
|
||||
// to locate the likely very few `Literal`s which need fixing.
|
||||
export function jsonParseAst(programJson) {
|
||||
const program = JSON.parse(programJson);
|
||||
transformNode(program);
|
||||
const { node: program, fixes } = JSON.parse(programJson);
|
||||
for (const fixPath of fixes) {
|
||||
applyFix(program, fixPath);
|
||||
}
|
||||
return program;
|
||||
}
|
||||
|
||||
function transformNode(node) {
|
||||
if (!node) return;
|
||||
if (Array.isArray(node)) {
|
||||
for (const el of node) {
|
||||
transformNode(el);
|
||||
}
|
||||
return;
|
||||
function applyFix(program, fixPath) {
|
||||
let node = program;
|
||||
for (const key of fixPath) {
|
||||
node = node[key];
|
||||
}
|
||||
|
||||
if (node.type === 'Literal') {
|
||||
transformLiteral(node);
|
||||
return;
|
||||
}
|
||||
|
||||
const keys = visitorKeys[node.type];
|
||||
if (!keys) return;
|
||||
for (const key of keys) {
|
||||
transformNode(node[key]);
|
||||
}
|
||||
}
|
||||
|
||||
function transformLiteral(node) {
|
||||
// Set `value` field of `Literal`s for `BigInt`s and `RegExp`s.
|
||||
// This is not possible to do on Rust side, as neither can be represented correctly in JSON.
|
||||
if (node.bigint) {
|
||||
node.value = BigInt(node.bigint);
|
||||
} else if (node.regex) {
|
||||
} else {
|
||||
try {
|
||||
node.value = RegExp(node.regex.pattern, node.regex.flags);
|
||||
} catch (_err) {
|
||||
|
||||
Generated
+6
-32
@@ -6,12 +6,6 @@ settings:
|
||||
|
||||
catalogs:
|
||||
default:
|
||||
'@napi-rs/cli':
|
||||
specifier: 3.0.0-alpha.78
|
||||
version: 3.0.0-alpha.78
|
||||
typescript:
|
||||
specifier: 5.8.3
|
||||
version: 5.8.3
|
||||
vitest:
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2
|
||||
@@ -91,9 +85,6 @@ importers:
|
||||
'@napi-rs/wasm-runtime':
|
||||
specifier: ^0.2.7
|
||||
version: 0.2.9
|
||||
'@typescript-eslint/visitor-keys':
|
||||
specifier: ^8.31.1
|
||||
version: 8.31.1
|
||||
'@vitest/browser':
|
||||
specifier: 3.1.2
|
||||
version: 3.1.2(playwright@1.52.0)(vite@6.3.4(@types/node@22.15.3))(vitest@3.1.2)
|
||||
@@ -1310,14 +1301,6 @@ packages:
|
||||
'@types/vscode@1.93.0':
|
||||
resolution: {integrity: sha512-kUK6jAHSR5zY8ps42xuW89NLcBpw1kOabah7yv38J8MyiYuOHxLQBi0e7zeXbQgVefDy/mZZetqEFC+Fl5eIEQ==}
|
||||
|
||||
'@typescript-eslint/types@8.31.1':
|
||||
resolution: {integrity: sha512-SfepaEFUDQYRoA70DD9GtytljBePSj17qPxFHA/h3eg6lPTqGJ5mWOtbXCk1YrVU1cTJRd14nhaXWFu0l2troQ==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.31.1':
|
||||
resolution: {integrity: sha512-I+/rgqOVBn6f0o7NDTmAPWWC6NuqhV174lfYvAm9fUaWeiefLdux9/YI3/nLugEn9L8fcSi0XmpKi/r5u0nmpw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
'@typespec/ts-http-runtime@0.2.2':
|
||||
resolution: {integrity: sha512-Gz/Sm64+Sq/vklJu1tt9t+4R2lvnud8NbTD/ZfpZtMiUX7YeVpCA8j6NSW8ptwcoLL+NmYANwqP8DV0q/bwl2w==}
|
||||
engines: {node: '>=18.0.0'}
|
||||
@@ -1840,10 +1823,6 @@ packages:
|
||||
resolution: {integrity: sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==}
|
||||
engines: {node: '>=10'}
|
||||
|
||||
eslint-visitor-keys@4.2.0:
|
||||
resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==}
|
||||
engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
resolution: {integrity: sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==}
|
||||
|
||||
@@ -4037,13 +4016,6 @@ snapshots:
|
||||
|
||||
'@types/vscode@1.93.0': {}
|
||||
|
||||
'@typescript-eslint/types@8.31.1': {}
|
||||
|
||||
'@typescript-eslint/visitor-keys@8.31.1':
|
||||
dependencies:
|
||||
'@typescript-eslint/types': 8.31.1
|
||||
eslint-visitor-keys: 4.2.0
|
||||
|
||||
'@typespec/ts-http-runtime@0.2.2':
|
||||
dependencies:
|
||||
http-proxy-agent: 7.0.2
|
||||
@@ -4478,6 +4450,10 @@ snapshots:
|
||||
|
||||
css-what@6.1.0: {}
|
||||
|
||||
debug@4.4.0:
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
|
||||
debug@4.4.0(supports-color@8.1.1):
|
||||
dependencies:
|
||||
ms: 2.1.3
|
||||
@@ -4628,8 +4604,6 @@ snapshots:
|
||||
|
||||
escape-string-regexp@4.0.0: {}
|
||||
|
||||
eslint-visitor-keys@4.2.0: {}
|
||||
|
||||
estree-walker@3.0.3:
|
||||
dependencies:
|
||||
'@types/estree': 1.0.7
|
||||
@@ -5615,7 +5589,7 @@ snapshots:
|
||||
vite-node@3.1.2(@types/node@22.15.3):
|
||||
dependencies:
|
||||
cac: 6.7.14
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.0
|
||||
es-module-lexer: 1.7.0
|
||||
pathe: 2.0.3
|
||||
vite: 6.3.4(@types/node@22.15.3)
|
||||
@@ -5655,7 +5629,7 @@ snapshots:
|
||||
'@vitest/spy': 3.1.2
|
||||
'@vitest/utils': 3.1.2
|
||||
chai: 5.2.0
|
||||
debug: 4.4.0(supports-color@8.1.1)
|
||||
debug: 4.4.0
|
||||
expect-type: 1.2.1
|
||||
magic-string: 0.30.17
|
||||
pathe: 2.0.3
|
||||
|
||||
@@ -52,7 +52,7 @@ fn bench_estree(criterion: &mut Criterion) {
|
||||
span_converter.convert_program(&mut program);
|
||||
span_converter.convert_comments(&mut program.comments);
|
||||
|
||||
black_box(program.to_estree_ts_json());
|
||||
black_box(program.to_estree_ts_json_with_fixes());
|
||||
program
|
||||
});
|
||||
});
|
||||
|
||||
@@ -81,7 +81,7 @@ impl CompilerInterface for Driver {
|
||||
self.errors.push(OxcDiagnostic::error("SourceType must not be unambiguous."));
|
||||
}
|
||||
// Make sure serialization doesn't crash; also for code coverage.
|
||||
program.to_estree_ts_json();
|
||||
program.to_estree_ts_json_with_fixes();
|
||||
ControlFlow::Continue(())
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user