diff --git a/crates/oxc/src/compiler.rs b/crates/oxc/src/compiler.rs index 0688290570263..8e87ea5a39f52 100644 --- a/crates/oxc/src/compiler.rs +++ b/crates/oxc/src/compiler.rs @@ -156,12 +156,13 @@ pub trait CompilerInterface { /* Compress */ if let Some(options) = self.compress_options() { - self.compress(&allocator, &mut program, options); + self.compress(&allocator, &mut program, source_text, options); } /* Mangler */ - let mangler = self.mangle_options().map(|options| self.mangle(&mut program, options)); + let mangler = + self.mangle_options().map(|options| self.mangle(&mut program, source_text, options)); /* Codegen */ @@ -214,13 +215,19 @@ pub trait CompilerInterface { &self, allocator: &'a Allocator, program: &mut Program<'a>, + source_text: &str, options: CompressOptions, ) { - Compressor::new(allocator, options).build(program); + Compressor::new(allocator, options).build(program, source_text); } - fn mangle(&self, program: &mut Program<'_>, options: MangleOptions) -> Mangler { - Mangler::new().with_options(options).build(program) + fn mangle( + &self, + program: &mut Program<'_>, + source_text: &str, + options: MangleOptions, + ) -> Mangler { + Mangler::new().with_options(options).build(program, source_text) } fn codegen<'a>( diff --git a/crates/oxc_mangler/src/lib.rs b/crates/oxc_mangler/src/lib.rs index bfac0b49bef90..2122571e63e82 100644 --- a/crates/oxc_mangler/src/lib.rs +++ b/crates/oxc_mangler/src/lib.rs @@ -81,8 +81,8 @@ impl Mangler { } #[must_use] - pub fn build<'a>(mut self, program: &'a Program<'a>) -> Mangler { - let semantic = SemanticBuilder::new("").build(program).semantic; + pub fn build<'a>(mut self, program: &'a Program<'a>, source_text: &str) -> Mangler { + let semantic = SemanticBuilder::new(source_text).build(program).semantic; // Mangle the symbol table by computing slots from the scope tree. // A slot is the occurrence index of a binding identifier inside a scope. diff --git a/crates/oxc_minifier/examples/mangler.rs b/crates/oxc_minifier/examples/mangler.rs index 3e71995dbddfd..67b64df795b30 100644 --- a/crates/oxc_minifier/examples/mangler.rs +++ b/crates/oxc_minifier/examples/mangler.rs @@ -39,6 +39,6 @@ fn mangler(source_text: &str, source_type: SourceType, debug: bool) -> String { let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(ret.program); - let mangler = Mangler::new().with_options(MangleOptions { debug }).build(program); + let mangler = Mangler::new().with_options(MangleOptions { debug }).build(program, source_text); CodeGenerator::new().with_mangler(Some(mangler)).build(program).source_text } diff --git a/crates/oxc_minifier/examples/minifier.rs b/crates/oxc_minifier/examples/minifier.rs index 844c9db9073ea..da793bdd96dd3 100644 --- a/crates/oxc_minifier/examples/minifier.rs +++ b/crates/oxc_minifier/examples/minifier.rs @@ -39,6 +39,6 @@ fn minify(source_text: &str, source_type: SourceType, mangle: bool) -> String { let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(ret.program); let options = MinifierOptions { mangle, compress: CompressOptions::all_true() }; - let ret = Minifier::new(options).build(&allocator, program); + let ret = Minifier::new(options).build(&allocator, program, source_text); CodeGenerator::new().with_mangler(ret.mangler).build(program).source_text } diff --git a/crates/oxc_minifier/src/compressor.rs b/crates/oxc_minifier/src/compressor.rs index 3035c933fe381..f5a3b45545bda 100644 --- a/crates/oxc_minifier/src/compressor.rs +++ b/crates/oxc_minifier/src/compressor.rs @@ -20,9 +20,11 @@ impl<'a> Compressor<'a> { Self { allocator, options } } - pub fn build(self, program: &mut Program<'a>) { - let (symbols, scopes) = - SemanticBuilder::new("").build(program).semantic.into_symbol_table_and_scope_tree(); + pub fn build(self, program: &mut Program<'a>, source_text: &str) { + let (symbols, scopes) = SemanticBuilder::new(source_text) + .build(program) + .semantic + .into_symbol_table_and_scope_tree(); self.build_with_symbols_and_scopes(symbols, scopes, program); } diff --git a/crates/oxc_minifier/src/lib.rs b/crates/oxc_minifier/src/lib.rs index b1320f7de6af8..d4d3de9701875 100644 --- a/crates/oxc_minifier/src/lib.rs +++ b/crates/oxc_minifier/src/lib.rs @@ -47,9 +47,14 @@ impl Minifier { Self { options } } - pub fn build<'a>(self, allocator: &'a Allocator, program: &mut Program<'a>) -> MinifierReturn { - Compressor::new(allocator, self.options.compress).build(program); - let mangler = self.options.mangle.then(|| Mangler::default().build(program)); + pub fn build<'a>( + self, + allocator: &'a Allocator, + program: &mut Program<'a>, + source_text: &str, + ) -> MinifierReturn { + Compressor::new(allocator, self.options.compress).build(program, source_text); + let mangler = self.options.mangle.then(|| Mangler::default().build(program, source_text)); MinifierReturn { mangler } } } diff --git a/crates/oxc_minifier/tests/mangler/mod.rs b/crates/oxc_minifier/tests/mangler/mod.rs index 83459ef608f19..bad6a7c48ccc6 100644 --- a/crates/oxc_minifier/tests/mangler/mod.rs +++ b/crates/oxc_minifier/tests/mangler/mod.rs @@ -11,7 +11,7 @@ fn mangle(source_text: &str) -> String { let source_type = SourceType::mjs(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = ret.program; - let mangler = Mangler::new().build(&program); + let mangler = Mangler::new().build(&program, source_text); CodeGenerator::new().with_mangler(Some(mangler)).build(&program).source_text } diff --git a/crates/oxc_minifier/tests/mod.rs b/crates/oxc_minifier/tests/mod.rs index 03b1b40625b5a..05176ba44f449 100644 --- a/crates/oxc_minifier/tests/mod.rs +++ b/crates/oxc_minifier/tests/mod.rs @@ -23,7 +23,7 @@ fn run(source_text: &str, source_type: SourceType, options: Option SemanticBuilder<'a> { let scope_id = self.scope.add_scope(None, NodeId::DUMMY, ScopeFlags::Top); program.scope_id.set(Some(scope_id)); } else { - // Count the number of nodes, scopes, symbols, and references. + // Estimate the number of nodes, scopes, symbols, and references. // Use these counts to reserve sufficient capacity in `AstNodes`, `ScopeTree` // and `SymbolTable` to store them. // This means that as we traverse the AST and fill up these structures with data, @@ -229,10 +229,8 @@ impl<'a> SemanticBuilder<'a> { // involves copying all the memory from the old allocation to the new one. // For large source files, these structures are very large, so growth is very costly // as it involves copying massive chunks of memory. - // Avoiding this growth produces up to 30% perf boost on our benchmarks. - // TODO: It would be even more efficient to calculate counts in parser to avoid - // this extra AST traversal. - let counts = Counts::count(program); + // Avoiding this growth produces up to 40% perf boost on our benchmarks. + let counts = Counts::count(program, self.source_text); self.nodes.reserve(counts.nodes); self.scope.reserve(counts.scopes); self.symbols.reserve(counts.symbols, counts.references); @@ -243,11 +241,12 @@ impl<'a> SemanticBuilder<'a> { // Check that estimated counts accurately #[cfg(debug_assertions)] { + #[allow(clippy::cast_possible_truncation)] let actual_counts = Counts { - nodes: self.nodes.len(), - scopes: self.scope.len(), - symbols: self.symbols.len(), - references: self.symbols.references.len(), + nodes: self.nodes.len() as u32, + scopes: self.scope.len() as u32, + symbols: self.symbols.len() as u32, + references: self.symbols.references.len() as u32, }; Counts::assert_accurate(&actual_counts, &counts); } diff --git a/crates/oxc_semantic/src/counter.rs b/crates/oxc_semantic/src/counts/fallback.rs similarity index 72% rename from crates/oxc_semantic/src/counter.rs rename to crates/oxc_semantic/src/counts/fallback.rs index 947292b8ffdda..64dde06236fae 100644 --- a/crates/oxc_semantic/src/counter.rs +++ b/crates/oxc_semantic/src/counts/fallback.rs @@ -1,6 +1,10 @@ -//! Visitor to count nodes, scopes, symbols and references in AST. -//! These counts can be used to pre-allocate sufficient capacity in `AstNodes`, -//! `ScopeTree`, and `SymbolTable` to store info for all these items. +//! Counter to estimate counts of nodes, scopes, symbols and references. +//! +//! Produces an accurate count, by visiting AST and counting these items. +//! +//! Doing a full traverse of AST has a sizeable performance cost, but is necessary on platforms +//! which are 32-bit or do not have virtual memory (e.g. WASM) and so the "standard" version of +//! `Counts` is not suitable. use std::cell::Cell; @@ -13,38 +17,37 @@ use oxc_ast::{ }; use oxc_syntax::scope::{ScopeFlags, ScopeId}; +use super::assert_le; + #[derive(Default, Debug)] -pub(crate) struct Counts { - pub nodes: usize, - pub scopes: usize, - pub symbols: usize, - pub references: usize, +pub struct Counts { + pub nodes: u32, + pub scopes: u32, + pub symbols: u32, + pub references: u32, } impl Counts { - pub fn count(program: &Program) -> Self { - let mut counts = Counts::default(); + /// Calculate counts by visiting AST + pub fn count(program: &Program, _source_text: &str) -> Self { + let mut counts = Self::default(); counts.visit_program(program); counts } + /// Assert that estimated counts were accurate #[cfg_attr(not(debug_assertions), expect(dead_code))] pub fn assert_accurate(actual: &Self, estimated: &Self) { assert_eq!(actual.nodes, estimated.nodes, "nodes count mismatch"); assert_eq!(actual.scopes, estimated.scopes, "scopes count mismatch"); - assert_eq!(actual.references, estimated.references, "references count mismatch"); // `Counts` may overestimate number of symbols, because multiple `BindingIdentifier`s // can result in only a single symbol. // e.g. `var x; var x;` = 2 x `BindingIdentifier` but 1 x symbol. - // This is not a big problem - allocating a `Vec` with excess capacity is cheap. + // This is not a big problem - allocating a `Vec` with excess capacity is fairly cheap. // It's allocating with *not enough* capacity which is costly, as then the `Vec` // will grow and reallocate. - assert!( - actual.symbols <= estimated.symbols, - "symbols count mismatch {} <= {}", - actual.symbols, - estimated.symbols - ); + assert_le!(actual.symbols, estimated.symbols, "symbols count mismatch"); + assert_eq!(actual.references, estimated.references, "references count mismatch"); } } diff --git a/crates/oxc_semantic/src/counts/mod.rs b/crates/oxc_semantic/src/counts/mod.rs new file mode 100644 index 0000000000000..a15e6904bb13a --- /dev/null +++ b/crates/oxc_semantic/src/counts/mod.rs @@ -0,0 +1,54 @@ +//! Counter to estimate counts of nodes, scopes, symbols and references. +//! +//! These counts can be used to pre-allocate sufficient capacity in `AstNodes`, +//! `ScopeTree`, and `SymbolTable`, to store data for all these items. +//! If sufficient capacity is not reserved in advance, these structures can grow and reallocate +//! during their construction, which involves copying large chunks of memory. +//! This produces a large performance cost - around 30% on our benchmarks for large source files. +//! +//! `Counts` has 2 implementations. +//! * `standard` - preferred version, for 64-bit platforms with virtual memory. +//! * `visitor` - fallback version, for 32-bit platforms, and platforms with no virtual memory (e.g. WASM). +//! +//! Please see docs in each module for the differences between them. + +#[cfg(all(target_pointer_width = "64", not(target_arch = "wasm32")))] +mod standard; +#[cfg(all(target_pointer_width = "64", not(target_arch = "wasm32")))] +pub use standard::Counts; + +#[cfg(not(all(target_pointer_width = "64", not(target_arch = "wasm32"))))] +mod fallback; +#[cfg(not(all(target_pointer_width = "64", not(target_arch = "wasm32"))))] +pub use fallback::Counts; + +/// Macro to assert that `left <= right` +macro_rules! assert_le { + ($left:expr, $right:expr, $($msg_args:tt)+) => { + match (&$left, &$right) { + (left, right) => if !(left <= right) { + panic!( + "assertion failed: `(left <= right)`\n left: `{:?}`,\n right: `{:?}`: {}", + left, right, + ::std::format_args!($($msg_args)+), + ); + } + } + }; + + ($left:expr, $right:expr) => { + match (&$left, &$right) { + (left, right) => if !(left <= right) { + panic!( + "assertion failed: `(left <= right)`\n left: `{:?}`,\n right: `{:?}`", + left, right, + ); + } + } + }; + + ($lhs:expr, $rhs:expr,) => { + assert_le!($lhs, $rhs); + }; +} +use assert_le; diff --git a/crates/oxc_semantic/src/counts/standard.rs b/crates/oxc_semantic/src/counts/standard.rs new file mode 100644 index 0000000000000..4731c27683087 --- /dev/null +++ b/crates/oxc_semantic/src/counts/standard.rs @@ -0,0 +1,83 @@ +//! Counter to estimate counts of nodes, scopes, symbols and references. +//! +//! Estimate counts based on size of source text. +//! +//! These will almost always be a large over-estimate, but will never be an under-estimate. +//! Not under-estimating is the most important thing, as the `Vec`s in `AstNodes`, `ScopeTree` +//! and `SymbolTable` will not need to resize during building `Semantic`, which avoids expensive +//! memory copying. +//! +//! This implementation of `Counts` can be used on 64-bit platforms with virtual memory, where it's +//! not a big problem to reserve excessively large blocks of virtual memory, because: +//! 1. The memory space is so large that it's almost impossible to exhaust. +//! 2. Allocating memory only consumes virtual memory, not physical memory. +//! +//! Note: Ideally, we'd shrink the allocations to fit once the actual size required is known. +//! But found that shrinking caused memory to be reallocated, which had a large perf cost +//! (~10% on semantic benchmarks). + +use std::cmp::max; + +use oxc_ast::ast::Program; + +use super::assert_le; + +#[derive(Default, Debug)] +pub struct Counts { + pub nodes: u32, + pub scopes: u32, + pub symbols: u32, + pub references: u32, +} + +impl Counts { + /// Calculate counts as probable over-estimates based on size of source text + pub fn count(_program: &Program, source_text: &str) -> Self { + #[allow(clippy::cast_possible_truncation)] + let source_len = source_text.len() as u32; + + // Calculate maximum number of nodes, scopes, symbols and references that's possible + // for given length of source code. + // These will almost always be a large over-estimate, but will never be an under-estimate. + + // The most node-intensive code is: + // `` = 0 bytes, 1 nodes + // `x` = 1 bytes, 3 nodes + // `x=x` = 3 bytes, 7 nodes + // `x=x=x` = 5 bytes, 11 nodes + #[allow(clippy::cast_lossless)] + let nodes = u32::try_from(source_len as u64 * 2 + 1).unwrap_or(u32::MAX); + + // The most scope-intensive code is: + // `` = 0 bytes, 1 scopes + // `{}` = 2 bytes, 2 scopes + // `{{}}` = 4 bytes, 3 scopes + // `{{{}}}` = 6 bytes, 4 scopes + let scopes = source_len / 2 + 1; + + // The most symbol-intensive code is: + // `` = 0 bytes, 0 symbols + // `a=>0` = 4 bytes, 1 symbols + // `(a,a)=>0` = 8 bytes, 2 symbols + // `var a` = 5 bytes, 1 symbols + // `var a,a` = 7 bytes, 2 symbols + let symbols = max(source_len / 2, 1) - 1; + + // The most reference-intensive code is: + // `a` = 1 bytes, 1 references + // `a,a` = 3 bytes, 2 references + // `a,a,a` = 5 bytes, 3 references + let references = source_len / 2 + 1; + + Self { nodes, scopes, symbols, references } + } + + /// Assert that estimated counts were not an under-estimate + #[cfg_attr(not(debug_assertions), expect(dead_code))] + pub fn assert_accurate(actual: &Self, estimated: &Self) { + assert_le!(actual.nodes, estimated.nodes, "nodes count mismatch"); + assert_le!(actual.scopes, estimated.scopes, "scopes count mismatch"); + assert_le!(actual.symbols, estimated.symbols, "symbols count mismatch"); + assert_le!(actual.references, estimated.references, "references count mismatch"); + } +} diff --git a/crates/oxc_semantic/src/lib.rs b/crates/oxc_semantic/src/lib.rs index c1a1f28415a02..27ea4cabd6c73 100644 --- a/crates/oxc_semantic/src/lib.rs +++ b/crates/oxc_semantic/src/lib.rs @@ -23,7 +23,7 @@ mod binder; mod builder; mod checker; mod class; -mod counter; +mod counts; mod diagnostics; mod jsdoc; mod label; diff --git a/crates/oxc_semantic/src/node.rs b/crates/oxc_semantic/src/node.rs index 3a478bacad6b5..c6d1c87899414 100644 --- a/crates/oxc_semantic/src/node.rs +++ b/crates/oxc_semantic/src/node.rs @@ -197,7 +197,8 @@ impl<'a> AstNodes<'a> { ast_node_id } - pub fn reserve(&mut self, additional: usize) { + pub fn reserve(&mut self, additional: u32) { + let additional = additional as usize; self.nodes.reserve(additional); self.parent_ids.reserve(additional); } diff --git a/crates/oxc_semantic/src/post_transform_checker.rs b/crates/oxc_semantic/src/post_transform_checker.rs index 9c51f23f34bd0..464b40fce369b 100644 --- a/crates/oxc_semantic/src/post_transform_checker.rs +++ b/crates/oxc_semantic/src/post_transform_checker.rs @@ -109,11 +109,14 @@ use crate::{ScopeTree, SemanticBuilder, SymbolTable}; type FxIndexMap = IndexMap>; -/// Check `ScopeTree` and `SymbolTable` are correct after transform +/// Check `ScopeTree` and `SymbolTable` are correct after transform. +/// +/// `code_after_transform` must be the *post-transform* AST printed to string. pub fn check_semantic_after_transform( symbols_after_transform: &SymbolTable, scopes_after_transform: &ScopeTree, program: &Program<'_>, + code_after_transform: &str, ) -> Option> { let mut errors = Errors::default(); @@ -129,7 +132,7 @@ pub fn check_semantic_after_transform( // so the cloned AST will be "clean" of all semantic data, as if it had come fresh from the parser. let allocator = Allocator::default(); let program = program.clone_in(&allocator); - let (symbols_rebuilt, scopes_rebuilt) = SemanticBuilder::new("") + let (symbols_rebuilt, scopes_rebuilt) = SemanticBuilder::new(code_after_transform) .with_scope_tree_child_ids(scopes_after_transform.has_child_ids()) .build(&program) .semantic diff --git a/crates/oxc_semantic/src/scope.rs b/crates/oxc_semantic/src/scope.rs index 51c33343a8f10..efd1f5f17049a 100644 --- a/crates/oxc_semantic/src/scope.rs +++ b/crates/oxc_semantic/src/scope.rs @@ -301,7 +301,8 @@ impl ScopeTree { } /// Reserve memory for an `additional` number of scopes. - pub fn reserve(&mut self, additional: usize) { + pub fn reserve(&mut self, additional: u32) { + let additional = additional as usize; self.parent_ids.reserve(additional); self.flags.reserve(additional); self.bindings.reserve(additional); diff --git a/crates/oxc_semantic/src/symbol.rs b/crates/oxc_semantic/src/symbol.rs index 2170dc7fc1183..7bb93e7c0e6f2 100644 --- a/crates/oxc_semantic/src/symbol.rs +++ b/crates/oxc_semantic/src/symbol.rs @@ -197,7 +197,9 @@ impl SymbolTable { reference_ids.swap_remove(index); } - pub fn reserve(&mut self, additional_symbols: usize, additional_references: usize) { + pub fn reserve(&mut self, additional_symbols: u32, additional_references: u32) { + let additional_symbols = additional_symbols as usize; + let additional_references = additional_references as usize; self.spans.reserve(additional_symbols); self.names.reserve(additional_symbols); self.flags.reserve(additional_symbols); diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index 24b3cd357feff..27f3dadb54fdc 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -266,7 +266,7 @@ impl Oxc { CompressOptions::all_false() }, }; - Minifier::new(options).build(&allocator, &mut program).mangler + Minifier::new(options).build(&allocator, &mut program, source_text).mangler } else { None }; diff --git a/tasks/benchmark/benches/minifier.rs b/tasks/benchmark/benches/minifier.rs index c316839277a88..3d46d57282029 100644 --- a/tasks/benchmark/benches/minifier.rs +++ b/tasks/benchmark/benches/minifier.rs @@ -18,7 +18,7 @@ fn bench_minifier(criterion: &mut Criterion) { let allocator = Allocator::default(); let program = Parser::new(&allocator, source_text, source_type).parse().program; let program = allocator.alloc(program); - Compressor::new(&allocator, options).build(program); + Compressor::new(&allocator, options).build(program, source_text); allocator }); }, diff --git a/tasks/benchmark/src/lib.rs b/tasks/benchmark/src/lib.rs index c92a9194a02ba..2f24259725377 100644 --- a/tasks/benchmark/src/lib.rs +++ b/tasks/benchmark/src/lib.rs @@ -2,8 +2,8 @@ use std::alloc::{GlobalAlloc, Layout, System}; pub use criterion::*; -#[global_allocator] -static GLOBAL: NeverGrowInPlaceAllocator = NeverGrowInPlaceAllocator; +// #[global_allocator] +// static GLOBAL: NeverGrowInPlaceAllocator = NeverGrowInPlaceAllocator; /// Global allocator for use in benchmarks. /// @@ -24,6 +24,7 @@ static GLOBAL: NeverGrowInPlaceAllocator = NeverGrowInPlaceAllocator; /// [`GlobalAlloc::realloc`] implementation which *never* grows in place. /// It therefore represents the "worse case scenario" for memory allocation performance. /// This behavior is consistent and predictable, and therefore stabilizes benchmark results. +#[expect(dead_code)] struct NeverGrowInPlaceAllocator; // SAFETY: Methods simply delegate to `System` allocator diff --git a/tasks/coverage/src/driver.rs b/tasks/coverage/src/driver.rs index acde33772b179..f725c1b57f379 100644 --- a/tasks/coverage/src/driver.rs +++ b/tasks/coverage/src/driver.rs @@ -6,7 +6,7 @@ use oxc::{ ast::{Program, RegExpFlags}, Trivias, }, - codegen::CodegenOptions, + codegen::{CodeGenerator, CodegenOptions}, diagnostics::OxcDiagnostic, minifier::CompressOptions, parser::{ParseOptions, ParserReturn}, @@ -101,10 +101,16 @@ impl CompilerInterface for Driver { transformer_return: &mut TransformerReturn, ) -> ControlFlow<()> { if self.check_semantic { + let code = CodeGenerator::new() + .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }) + .build(program) + .source_text; + if let Some(errors) = check_semantic_after_transform( &transformer_return.symbols, &transformer_return.scopes, program, + &code, ) { self.errors.extend(errors); return ControlFlow::Break(()); diff --git a/tasks/minsize/src/lib.rs b/tasks/minsize/src/lib.rs index b976a26fc5663..18c7a025458c4 100644 --- a/tasks/minsize/src/lib.rs +++ b/tasks/minsize/src/lib.rs @@ -110,7 +110,7 @@ fn minify(source_text: &str, source_type: SourceType, options: MinifierOptions) let allocator = Allocator::default(); let ret = Parser::new(&allocator, source_text, source_type).parse(); let program = allocator.alloc(ret.program); - let ret = Minifier::new(options).build(&allocator, program); + let ret = Minifier::new(options).build(&allocator, program, source_text); CodeGenerator::new() .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }) .with_mangler(ret.mangler) diff --git a/tasks/transform_conformance/src/driver.rs b/tasks/transform_conformance/src/driver.rs index ce083caa4ccf7..8f3f2a4945b0a 100644 --- a/tasks/transform_conformance/src/driver.rs +++ b/tasks/transform_conformance/src/driver.rs @@ -2,6 +2,7 @@ use std::{mem, ops::ControlFlow, path::Path}; use oxc::{ ast::ast::Program, + codegen::{CodeGenerator, CodegenOptions}, diagnostics::OxcDiagnostic, semantic::post_transform_checker::check_semantic_after_transform, span::SourceType, @@ -43,10 +44,16 @@ impl CompilerInterface for Driver { transformer_return: &mut TransformerReturn, ) -> ControlFlow<()> { if self.check_semantic { + let code = CodeGenerator::new() + .with_options(CodegenOptions { minify: true, ..CodegenOptions::default() }) + .build(program) + .source_text; + if let Some(errors) = check_semantic_after_transform( &transformer_return.symbols, &transformer_return.scopes, program, + &code, ) { self.errors.extend(errors); }