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:
overlookmotel committed 2025-05-06 02:34:24 +00:00
1 parent 33969ac361
commit 49a6f97388
17 files changed
+309 -449

No files matched your search

+42 -8
View File
@@ -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);
}
}
+1 -1
View File
@@ -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 }
+30
View File
@@ -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 {
+120 -4
View File
@@ -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);
}
+29 -15
View File
@@ -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) {
+34 -7
View File
@@ -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);
}
-29
View File
@@ -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`);
-143
View File
@@ -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;
-143
View File
@@ -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;
+1 -4
View File
@@ -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",
+4 -4
View File
@@ -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.
+2 -2
View File
@@ -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
View File
@@ -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
View File
@@ -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) {
+6 -32
View File
@@ -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
+1 -1
View File
@@ -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
});
});
+1 -1
View File
@@ -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(())
}