From 1b3b100475cb89f703afa0481bb11934675e75dc Mon Sep 17 00:00:00 2001 From: Boshen Date: Tue, 17 Oct 2023 14:52:51 +0800 Subject: [PATCH] feat(transformer_conformance): read plugins options from babel `options.json` (#1006) This PR correctly handles babel `options.json` such as https://github.com/babel/babel/blob/main/packages/babel-plugin-transform-nullish-coalescing-operator/test/fixtures/assumption-noDocumentAll/options.json --- Cargo.lock | 7 + crates/oxc_syntax/Cargo.toml | 8 +- crates/oxc_syntax/src/assumptions.rs | 15 + crates/oxc_syntax/src/lib.rs | 1 + crates/oxc_transformer/Cargo.toml | 2 + .../oxc_transformer/examples/transformer.rs | 11 +- .../src/es2015/shorthand_properties.rs | 9 +- .../src/es2016/exponentiation_operator.rs | 17 +- .../src/es2019/optional_catch_binding.rs | 9 +- crates/oxc_transformer/src/es2020/mod.rs | 4 +- .../src/es2020/nullish_coalescing_operator.rs | 29 +- .../es2021/logical_assignment_operators.rs | 9 +- .../src/es2022/class_static_block.rs | 9 +- crates/oxc_transformer/src/lib.rs | 29 +- crates/oxc_transformer/src/options.rs | 45 ++- crates/oxc_transformer/src/react_jsx/mod.rs | 17 +- .../src/regexp/regexp_flags.rs | 53 +-- crates/oxc_wasm/src/lib.rs | 9 +- tasks/common/Cargo.toml | 4 + tasks/common/src/babel.rs | 94 +++++ tasks/common/src/lib.rs | 4 +- tasks/coverage/src/babel.rs | 76 +--- tasks/transform_conformance/Cargo.toml | 7 +- tasks/transform_conformance/babel.snap.md | 15 +- tasks/transform_conformance/src/lib.rs | 354 ++++++++++-------- tasks/transform_conformance/src/main.rs | 6 +- 26 files changed, 497 insertions(+), 346 deletions(-) create mode 100644 crates/oxc_syntax/src/assumptions.rs create mode 100644 tasks/common/src/babel.rs diff --git a/Cargo.lock b/Cargo.lock index c4262ff57..50d609349 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1793,7 +1793,10 @@ dependencies = [ name = "oxc_tasks_common" version = "0.0.0" dependencies = [ + "oxc_syntax", "project-root", + "serde", + "serde_json", "ureq", "url", ] @@ -1809,9 +1812,12 @@ dependencies = [ "oxc_parser", "oxc_semantic", "oxc_span", + "oxc_syntax", "oxc_tasks_common", "oxc_transformer", "pico-args", + "serde", + "serde_json", "walkdir", ] @@ -1826,6 +1832,7 @@ dependencies = [ "oxc_semantic", "oxc_span", "oxc_syntax", + "serde", ] [[package]] diff --git a/crates/oxc_syntax/Cargo.toml b/crates/oxc_syntax/Cargo.toml index 4acb7e1bf..624491691 100644 --- a/crates/oxc_syntax/Cargo.toml +++ b/crates/oxc_syntax/Cargo.toml @@ -15,10 +15,6 @@ categories.workspace = true [lib] doctest = false -[features] -default = [] -serde = ["dep:serde"] - [dependencies] oxc_index = { workspace = true } oxc_span = { workspace = true } @@ -29,3 +25,7 @@ bitflags = { workspace = true } rustc-hash = { workspace = true } indexmap = { workspace = true } dashmap = { workspace = true } + +[features] +default = [] +serde = ["dep:serde"] diff --git a/crates/oxc_syntax/src/assumptions.rs b/crates/oxc_syntax/src/assumptions.rs new file mode 100644 index 000000000..9cf584a89 --- /dev/null +++ b/crates/oxc_syntax/src/assumptions.rs @@ -0,0 +1,15 @@ +#[cfg(feature = "serde")] +use serde::Deserialize; + +/// Compiler assumptions +/// +/// See +#[derive(Debug, Default, Clone, Copy)] +#[cfg_attr(feature = "serde", derive(Deserialize))] +#[cfg_attr(feature = "serde", serde(rename_all = "camelCase"))] +pub struct CompilerAssumptions { + /// When using operators that check for null or undefined, assume that they are never used with the special value document.all. + /// See . + #[cfg_attr(feature = "serde", serde(default))] + pub no_document_all: bool, +} diff --git a/crates/oxc_syntax/src/lib.rs b/crates/oxc_syntax/src/lib.rs index e96c8a9d8..08427b564 100644 --- a/crates/oxc_syntax/src/lib.rs +++ b/crates/oxc_syntax/src/lib.rs @@ -1,5 +1,6 @@ //! Common code for JavaScript Syntax +pub mod assumptions; pub mod identifier; pub mod module_record; pub mod operator; diff --git a/crates/oxc_transformer/Cargo.toml b/crates/oxc_transformer/Cargo.toml index e567bbe16..2f870abb4 100644 --- a/crates/oxc_transformer/Cargo.toml +++ b/crates/oxc_transformer/Cargo.toml @@ -22,6 +22,8 @@ oxc_allocator = { workspace = true } oxc_syntax = { workspace = true } oxc_semantic = { workspace = true } +serde = { workspace = true } + [dev-dependencies] oxc_parser = { workspace = true } oxc_codegen = { workspace = true } diff --git a/crates/oxc_transformer/examples/transformer.rs b/crates/oxc_transformer/examples/transformer.rs index 0bccb340d..9f9819daa 100644 --- a/crates/oxc_transformer/examples/transformer.rs +++ b/crates/oxc_transformer/examples/transformer.rs @@ -5,9 +5,7 @@ use oxc_codegen::{Codegen, CodegenOptions}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::SourceType; -use oxc_transformer::{ - Assumptions, TransformOptions, TransformReactOptions, TransformTarget, Transformer, -}; +use oxc_transformer::{TransformOptions, TransformTarget, Transformer}; // Instruction: // create a `test.js`, @@ -40,11 +38,8 @@ fn main() { let symbols = Rc::new(RefCell::new(symbols)); let program = allocator.alloc(ret.program); - let transform_options = TransformOptions { - target: TransformTarget::ES2015, - react: Some(TransformReactOptions::default()), - assumptions: Assumptions::default(), - }; + let transform_options = + TransformOptions { target: TransformTarget::ES2015, ..TransformOptions::default() }; Transformer::new(&allocator, source_type, &symbols, transform_options).build(program); let printed = Codegen::::new(source_text.len(), codegen_options).build(program); println!("Transformed:\n"); diff --git a/crates/oxc_transformer/src/es2015/shorthand_properties.rs b/crates/oxc_transformer/src/es2015/shorthand_properties.rs index 944241289..285b50f2b 100644 --- a/crates/oxc_transformer/src/es2015/shorthand_properties.rs +++ b/crates/oxc_transformer/src/es2015/shorthand_properties.rs @@ -1,7 +1,9 @@ +use std::rc::Rc; + use oxc_ast::{ast::*, AstBuilder}; use oxc_span::GetSpan; -use std::rc::Rc; +use crate::options::{TransformOptions, TransformTarget}; /// ES2015: Shorthand Properties /// @@ -13,8 +15,9 @@ pub struct ShorthandProperties<'a> { } impl<'a> ShorthandProperties<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } + pub fn new(ast: Rc>, options: &TransformOptions) -> Option { + (options.target < TransformTarget::ES2015 || options.shorthand_properties) + .then(|| Self { ast }) } pub fn transform_object_property<'b>(&mut self, obj_prop: &'b mut ObjectProperty<'a>) { diff --git a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs index 70d610786..d9eab57c6 100644 --- a/crates/oxc_transformer/src/es2016/exponentiation_operator.rs +++ b/crates/oxc_transformer/src/es2016/exponentiation_operator.rs @@ -6,7 +6,10 @@ use oxc_semantic::SymbolTable; use oxc_span::{Atom, Span}; use oxc_syntax::operator::{AssignmentOperator, BinaryOperator}; -use crate::utils::CreateVars; +use crate::{ + options::{TransformOptions, TransformTarget}, + utils::CreateVars, +}; /// ES2016: Exponentiation Operator /// @@ -36,9 +39,15 @@ impl<'a> CreateVars<'a> for ExponentiationOperator<'a> { } impl<'a> ExponentiationOperator<'a> { - pub fn new(ast: Rc>, symbols: Rc>) -> Self { - let vars = ast.new_vec(); - Self { ast, symbols, vars } + pub fn new( + ast: Rc>, + symbols: Rc>, + options: &TransformOptions, + ) -> Option { + (options.target < TransformTarget::ES2016 || options.exponentiation_operator).then(|| { + let vars = ast.new_vec(); + Self { ast, symbols, vars } + }) } pub fn transform_expression(&mut self, expr: &mut Expression<'a>) { diff --git a/crates/oxc_transformer/src/es2019/optional_catch_binding.rs b/crates/oxc_transformer/src/es2019/optional_catch_binding.rs index 46b15f28c..54b988303 100644 --- a/crates/oxc_transformer/src/es2019/optional_catch_binding.rs +++ b/crates/oxc_transformer/src/es2019/optional_catch_binding.rs @@ -1,7 +1,9 @@ +use std::rc::Rc; + use oxc_ast::{ast::*, AstBuilder}; use oxc_span::Span; -use std::rc::Rc; +use crate::options::{TransformOptions, TransformTarget}; /// ES2019: Optional Catch Binding /// @@ -13,8 +15,9 @@ pub struct OptionalCatchBinding<'a> { } impl<'a> OptionalCatchBinding<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } + pub fn new(ast: Rc>, options: &TransformOptions) -> Option { + (options.target < TransformTarget::ES2019 || options.optional_catch_binding) + .then(|| Self { ast }) } pub fn transform_catch_clause<'b>(&mut self, clause: &'b mut CatchClause<'a>) { diff --git a/crates/oxc_transformer/src/es2020/mod.rs b/crates/oxc_transformer/src/es2020/mod.rs index d10ee4116..f1442912c 100644 --- a/crates/oxc_transformer/src/es2020/mod.rs +++ b/crates/oxc_transformer/src/es2020/mod.rs @@ -1,3 +1,5 @@ mod nullish_coalescing_operator; -pub use nullish_coalescing_operator::NullishCoalescingOperator; +pub use nullish_coalescing_operator::{ + NullishCoalescingOperator, NullishCoalescingOperatorOptions, +}; diff --git a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs index 2bd6ed0fa..351be8c51 100644 --- a/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs +++ b/crates/oxc_transformer/src/es2020/nullish_coalescing_operator.rs @@ -1,3 +1,4 @@ +use serde::Deserialize; use std::{cell::RefCell, rc::Rc}; use oxc_allocator::Vec; @@ -6,7 +7,15 @@ use oxc_semantic::SymbolTable; use oxc_span::Span; use oxc_syntax::operator::{AssignmentOperator, BinaryOperator, LogicalOperator}; -use crate::{options::Assumptions, utils::CreateVars}; +use crate::{utils::CreateVars, TransformOptions, TransformTarget}; + +#[derive(Debug, Default, Clone, Copy, Deserialize)] +pub struct NullishCoalescingOperatorOptions { + /// When true, this transform will pretend `document.all` does not exist, + /// and perform loose equality checks with null instead of strict equality checks against both null and undefined. + #[serde(default)] + loose: bool, +} /// ES2020: Nullish Coalescing Operator /// @@ -14,9 +23,10 @@ use crate::{options::Assumptions, utils::CreateVars}; /// * /// * pub struct NullishCoalescingOperator<'a> { + no_document_all: bool, + ast: Rc>, symbols: Rc>, - assumptions: Assumptions, vars: Vec<'a, VariableDeclarator<'a>>, } @@ -34,10 +44,15 @@ impl<'a> NullishCoalescingOperator<'a> { pub fn new( ast: Rc>, symbols: Rc>, - assumptions: Assumptions, - ) -> Self { - let vars = ast.new_vec(); - Self { ast, symbols, assumptions, vars } + options: &TransformOptions, + ) -> Option { + (options.target < TransformTarget::ES2020 || options.nullish_coalescing_operator.is_some()) + .then(|| { + let no_document_all = options.assumptions.no_document_all + || options.nullish_coalescing_operator.is_some_and(|o| o.loose); + let vars = ast.new_vec(); + Self { no_document_all, ast, symbols, vars } + }) } pub fn transform_expression(&mut self, expr: &mut Expression<'a>) { @@ -67,7 +82,7 @@ impl<'a> NullishCoalescingOperator<'a> { self.ast.assignment_expression(span, AssignmentOperator::Assign, left, right); }; - let test = if self.assumptions.no_document_all { + let test = if self.no_document_all { let null = self.ast.literal_null_expression(NullLiteral::new(span)); self.ast.binary_expression(span, assignment, BinaryOperator::Inequality, null) } else { diff --git a/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs index 8e4aa20f8..a0976a6f9 100644 --- a/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs +++ b/crates/oxc_transformer/src/es2021/logical_assignment_operators.rs @@ -1,8 +1,10 @@ +use std::rc::Rc; + use oxc_ast::{ast::*, AstBuilder}; use oxc_span::Span; use oxc_syntax::operator::{AssignmentOperator, LogicalOperator}; -use std::rc::Rc; +use crate::options::{TransformOptions, TransformTarget}; /// ES2021: Logical Assignment Operators /// @@ -14,8 +16,9 @@ pub struct LogicalAssignmentOperators<'a> { } impl<'a> LogicalAssignmentOperators<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } + pub fn new(ast: Rc>, options: &TransformOptions) -> Option { + (options.target < TransformTarget::ES2021 || options.logical_assignment_operators) + .then(|| Self { ast }) } pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) { diff --git a/crates/oxc_transformer/src/es2022/class_static_block.rs b/crates/oxc_transformer/src/es2022/class_static_block.rs index 461e35fc2..7f9817dfe 100644 --- a/crates/oxc_transformer/src/es2022/class_static_block.rs +++ b/crates/oxc_transformer/src/es2022/class_static_block.rs @@ -1,7 +1,9 @@ +use std::{collections::HashSet, rc::Rc}; + use oxc_ast::{ast::*, AstBuilder}; use oxc_span::{Atom, Span}; -use std::{collections::HashSet, rc::Rc}; +use crate::options::{TransformOptions, TransformTarget}; /// ES2022: Class Static Block /// @@ -13,8 +15,9 @@ pub struct ClassStaticBlock<'a> { } impl<'a> ClassStaticBlock<'a> { - pub fn new(ast: Rc>) -> Self { - Self { ast } + pub fn new(ast: Rc>, options: &TransformOptions) -> Option { + (options.target < TransformTarget::ES2022 || options.class_static_block) + .then(|| Self { ast }) } pub fn transform_class_body<'b>(&mut self, class_body: &'b mut ClassBody<'a>) { diff --git a/crates/oxc_transformer/src/lib.rs b/crates/oxc_transformer/src/lib.rs index 050cd5783..bebdb6b01 100644 --- a/crates/oxc_transformer/src/lib.rs +++ b/crates/oxc_transformer/src/lib.rs @@ -30,12 +30,15 @@ use oxc_span::SourceType; use crate::{ es2015::ShorthandProperties, es2016::ExponentiationOperator, es2019::OptionalCatchBinding, - es2020::NullishCoalescingOperator, es2021::LogicalAssignmentOperators, react_jsx::ReactJsx, - regexp::RegexpFlags, typescript::TypeScript, utils::CreateVars, + es2020::NullishCoalescingOperator, es2021::LogicalAssignmentOperators, + es2022::ClassStaticBlock, react_jsx::ReactJsx, regexp::RegexpFlags, typescript::TypeScript, + utils::CreateVars, }; -pub use crate::options::{ - Assumptions, TransformOptions, TransformReactOptions, TransformReactRuntime, TransformTarget, +pub use crate::{ + es2020::NullishCoalescingOperatorOptions, + options::{TransformOptions, TransformTarget}, + react_jsx::{ReactJsxOptions, ReactJsxRuntime}, }; #[derive(Default)] @@ -46,7 +49,7 @@ pub struct Transformer<'a> { react_jsx: Option>, regexp_flags: Option>, // es2022 - es2022_class_static_block: Option>, + es2022_class_static_block: Option>, // es2021 es2021_logical_assignment_operators: Option>, // es2020 @@ -70,14 +73,14 @@ impl<'a> Transformer<'a> { let ast = Rc::new(AstBuilder::new(allocator)); Self { typescript: source_type.is_typescript().then(|| TypeScript::new(Rc::clone(&ast))), - react_jsx: options.react.map(|options| ReactJsx::new(Rc::clone(&ast), options)), - regexp_flags: RegexpFlags::new(Rc::clone(&ast), options.target), - es2022_class_static_block: (options.target < TransformTarget::ES2022).then(|| es2022::ClassStaticBlock::new(Rc::clone(&ast))), - es2021_logical_assignment_operators: (options.target < TransformTarget::ES2021).then(|| LogicalAssignmentOperators::new(Rc::clone(&ast))), - es2020_nullish_coalescing_operators: (options.target < TransformTarget::ES2020).then(|| NullishCoalescingOperator::new(Rc::clone(&ast), Rc::clone(symbols), options.assumptions)), - es2019_optional_catch_binding: (options.target < TransformTarget::ES2019).then(|| OptionalCatchBinding::new(Rc::clone(&ast))), - es2016_exponentiation_operator: (options.target < TransformTarget::ES2016).then(|| ExponentiationOperator::new(Rc::clone(&ast), Rc::clone(symbols))), - es2015_shorthand_properties: (options.target < TransformTarget::ES2015).then(|| ShorthandProperties::new(Rc::clone(&ast))), + react_jsx: options.react_jsx.map(|options| ReactJsx::new(Rc::clone(&ast), options)), + regexp_flags: RegexpFlags::new(Rc::clone(&ast), &options), + es2022_class_static_block: es2022::ClassStaticBlock::new(Rc::clone(&ast), &options), + es2021_logical_assignment_operators: LogicalAssignmentOperators::new(Rc::clone(&ast), &options), + es2020_nullish_coalescing_operators: NullishCoalescingOperator::new(Rc::clone(&ast), Rc::clone(symbols), &options), + es2019_optional_catch_binding: OptionalCatchBinding::new(Rc::clone(&ast), &options), + es2016_exponentiation_operator: ExponentiationOperator::new(Rc::clone(&ast), Rc::clone(symbols), &options), + es2015_shorthand_properties: ShorthandProperties::new(Rc::clone(&ast), &options), } } diff --git a/crates/oxc_transformer/src/options.rs b/crates/oxc_transformer/src/options.rs index e79c749d9..510b47ea7 100644 --- a/crates/oxc_transformer/src/options.rs +++ b/crates/oxc_transformer/src/options.rs @@ -1,8 +1,27 @@ +use oxc_syntax::assumptions::CompilerAssumptions; + +use crate::{es2020::NullishCoalescingOperatorOptions, react_jsx::ReactJsxOptions}; + #[derive(Debug, Default, Clone, Copy)] pub struct TransformOptions { pub target: TransformTarget, - pub react: Option, - pub assumptions: Assumptions, + pub assumptions: CompilerAssumptions, + + pub react_jsx: Option, + + // es2022 + pub class_static_block: bool, + // es2021 + pub logical_assignment_operators: bool, + // es2020 + pub nullish_coalescing_operator: Option, + // es2019 + pub optional_catch_binding: bool, + // es2016 + pub exponentiation_operator: bool, + // es2015 + pub shorthand_properties: bool, + pub sticky_regex: bool, } /// See @@ -20,25 +39,3 @@ pub enum TransformTarget { #[default] ESNext, } - -#[derive(Debug, Default, Clone, Copy)] -pub struct TransformReactOptions { - _runtime: TransformReactRuntime, -} - -#[derive(Debug, Default, Clone, Copy)] -pub enum TransformReactRuntime { - #[default] - Classic, - Automatic, -} - -/// Compiler assumptions -/// -/// See -#[derive(Debug, Default, Clone, Copy)] -pub struct Assumptions { - /// When using operators that check for null or undefined, assume that they are never used with the special value document.all. - /// See . - pub no_document_all: bool, -} diff --git a/crates/oxc_transformer/src/react_jsx/mod.rs b/crates/oxc_transformer/src/react_jsx/mod.rs index a7fcedf78..37483f049 100644 --- a/crates/oxc_transformer/src/react_jsx/mod.rs +++ b/crates/oxc_transformer/src/react_jsx/mod.rs @@ -2,7 +2,18 @@ use std::rc::Rc; use oxc_ast::AstBuilder; -use crate::TransformReactOptions; +#[derive(Debug, Default, Clone, Copy)] +pub struct ReactJsxOptions { + _runtime: ReactJsxRuntime, +} + +#[derive(Debug, Default, Clone, Copy)] +pub enum ReactJsxRuntime { + #[default] + Classic, + #[allow(unused)] + Automatic, +} /// Transform React JSX /// @@ -11,11 +22,11 @@ use crate::TransformReactOptions; /// * pub struct ReactJsx<'a> { _ast: Rc>, - _options: TransformReactOptions, + _options: ReactJsxOptions, } impl<'a> ReactJsx<'a> { - pub fn new(_ast: Rc>, _options: TransformReactOptions) -> Self { + pub fn new(_ast: Rc>, _options: ReactJsxOptions) -> Self { Self { _ast, _options } } } diff --git a/crates/oxc_transformer/src/regexp/regexp_flags.rs b/crates/oxc_transformer/src/regexp/regexp_flags.rs index 46989a448..8600c8e66 100644 --- a/crates/oxc_transformer/src/regexp/regexp_flags.rs +++ b/crates/oxc_transformer/src/regexp/regexp_flags.rs @@ -3,7 +3,7 @@ use oxc_span::{Atom, Span}; use std::rc::Rc; -use crate::TransformTarget; +use crate::{TransformOptions, TransformTarget}; /// Transforms unsupported regex flags into Regex constructors. /// @@ -20,11 +20,36 @@ pub struct RegexpFlags<'a> { } impl<'a> RegexpFlags<'a> { - pub fn new(ast: Rc>, transform_target: TransformTarget) -> Option { - let transform_flags = Self::from_transform_target(transform_target); + pub fn new(ast: Rc>, options: &TransformOptions) -> Option { + let transform_flags = Self::from_transform_target(options); (!transform_flags.is_empty()).then(|| Self { ast, transform_flags }) } + fn from_transform_target(options: &TransformOptions) -> RegExpFlags { + let target = options.target; + let mut flag = RegExpFlags::empty(); + if target < TransformTarget::ES2015 || options.sticky_regex { + flag |= RegExpFlags::Y; + } + if target < TransformTarget::ES2015 { + flag |= RegExpFlags::U; + } + if target < TransformTarget::ES2018 { + flag |= RegExpFlags::S; + } + if target < TransformTarget::ES2022 { + flag |= RegExpFlags::D; + } + if target < TransformTarget::ES2024 { + flag |= RegExpFlags::V; + } + if target < TransformTarget::ESNext { + flag |= RegExpFlags::I; + flag |= RegExpFlags::M; + } + flag + } + // `/regex/flags` -> `new RegExp('regex', 'flags')` pub fn transform_expression(&self, expr: &mut Expression<'a>) { let Expression::RegExpLiteral(literal) = expr else { return }; @@ -43,26 +68,4 @@ impl<'a> RegexpFlags<'a> { arguments.push(Argument::Expression(flags_literal)); *expr = self.ast.new_expression(Span::default(), callee, arguments, None); } - - fn from_transform_target(value: TransformTarget) -> RegExpFlags { - let mut flag = RegExpFlags::empty(); - if value < TransformTarget::ES2015 { - flag |= RegExpFlags::Y; - flag |= RegExpFlags::U; - } - if value < TransformTarget::ES2018 { - flag |= RegExpFlags::S; - } - if value < TransformTarget::ES2022 { - flag |= RegExpFlags::D; - } - if value < TransformTarget::ES2024 { - flag |= RegExpFlags::V; - } - if value < TransformTarget::ESNext { - flag |= RegExpFlags::I; - flag |= RegExpFlags::M; - } - flag - } } diff --git a/crates/oxc_wasm/src/lib.rs b/crates/oxc_wasm/src/lib.rs index f59d226e5..3083fadf3 100644 --- a/crates/oxc_wasm/src/lib.rs +++ b/crates/oxc_wasm/src/lib.rs @@ -11,7 +11,7 @@ use oxc::{ parser::{Parser, ParserReturn}, semantic::{SemanticBuilder, SemanticBuilderReturn}, span::SourceType, - transformer::{Assumptions, TransformOptions, TransformTarget, Transformer}, + transformer::{TransformOptions, TransformTarget, Transformer}, }; use oxc_linter::{LintContext, Linter}; use oxc_query::{schema, Adapter, SCHEMA_TEXT}; @@ -215,11 +215,8 @@ impl Oxc { let semantic = SemanticBuilder::new(source_text, source_type).build(program).semantic; let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree(); let symbols = Rc::new(RefCell::new(symbols)); - let options = TransformOptions { - target: TransformTarget::ES2015, - react: None, - assumptions: Assumptions::default(), - }; + let options = + TransformOptions { target: TransformTarget::ES2015, ..TransformOptions::default() }; Transformer::new(&allocator, source_type, &symbols, options).build(program); } diff --git a/tasks/common/Cargo.toml b/tasks/common/Cargo.toml index c8e93577f..aa46656ee 100644 --- a/tasks/common/Cargo.toml +++ b/tasks/common/Cargo.toml @@ -9,7 +9,11 @@ license.workspace = true doctest = false [dependencies] +oxc_syntax = { workspace = true, features = ["serde"] } + project-root = { workspace = true } +serde = { workspace = true, features = ["derive"] } +serde_json = { workspace = true } ureq = { workspace = true } url = { workspace = true } diff --git a/tasks/common/src/babel.rs b/tasks/common/src/babel.rs new file mode 100644 index 000000000..aac712592 --- /dev/null +++ b/tasks/common/src/babel.rs @@ -0,0 +1,94 @@ +use std::path::Path; + +use oxc_syntax::assumptions::CompilerAssumptions; +use serde::Deserialize; +use serde_json::Value; + +/// Babel options.json for tests +#[derive(Debug, Default, Clone, Deserialize)] +#[serde(rename_all = "camelCase")] +pub struct BabelOptions { + pub source_type: Option, + pub throws: Option, + #[serde(default)] + pub plugins: Vec, // Can be a string or an array + #[serde(default)] + pub allow_return_outside_function: bool, + #[serde(default)] + pub allow_await_outside_function: bool, + #[serde(default)] + pub allow_undeclared_exports: bool, + #[serde(default)] + pub assumptions: CompilerAssumptions, +} + +impl BabelOptions { + /// Read options.json and merge them with options.json from ancestors directories. + /// # Panics + pub fn from_path(path: &Path) -> Self { + let mut options_json: Option = None; + for path in path.ancestors().take(3) { + let file = path.join("options.json"); + if !file.exists() { + continue; + } + let file = std::fs::read_to_string(&file).unwrap(); + let new_json: Self = serde_json::from_str(&file).unwrap(); + if let Some(existing_json) = options_json.as_mut() { + if let Some(source_type) = new_json.source_type { + existing_json.source_type = Some(source_type); + } + if let Some(throws) = new_json.throws { + existing_json.throws = Some(throws); + } + existing_json.plugins.extend(new_json.plugins); + } else { + options_json = Some(new_json); + } + } + options_json.unwrap_or_default() + } + + pub fn is_jsx(&self) -> bool { + self.plugins.iter().any(|v| v.as_str().is_some_and(|v| v == "jsx")) + } + + pub fn is_typescript(&self) -> bool { + self.plugins.iter().any(|v| { + let string_value = v.as_str().is_some_and(|v| v == "typescript"); + let array_value = v.get(0).and_then(Value::as_str).is_some_and(|s| s == "typescript"); + string_value || array_value + }) + } + + pub fn is_typescript_definition(&self) -> bool { + self.plugins.iter().filter_map(Value::as_array).any(|p| { + let typescript = p.get(0).and_then(Value::as_str).is_some_and(|s| s == "typescript"); + let dts = p + .get(1) + .and_then(Value::as_object) + .and_then(|v| v.get("dts")) + .and_then(Value::as_bool) + .is_some_and(|v| v); + typescript && dts + }) + } + + pub fn is_module(&self) -> bool { + self.source_type.as_ref().map_or(false, |s| matches!(s.as_str(), "module" | "unambiguous")) + } + + /// Returns + /// * `Some` if the plugin exists without a config + /// * `Some>` if the plugin exists with a config + /// * `None` if the plugin does not exist + pub fn get_plugin(&self, name: &str) -> Option> { + self.plugins.iter().find_map(|v| match v { + Value::String(s) if s == name => Some(None), + Value::Array(a) if a.get(0).and_then(Value::as_str).is_some_and(|s| s == name) => { + Some(a.get(1).cloned()) + } + _ => None, + }) + } +} diff --git a/tasks/common/src/lib.rs b/tasks/common/src/lib.rs index 262cb3a4e..5a4344084 100644 --- a/tasks/common/src/lib.rs +++ b/tasks/common/src/lib.rs @@ -1,10 +1,10 @@ use std::path::{Path, PathBuf}; +mod babel; mod request; mod test_file; -pub use self::request::agent; -pub use self::test_file::*; +pub use crate::{babel::BabelOptions, request::agent, test_file::*}; /// # Panics /// Invalid Project Root diff --git a/tasks/coverage/src/babel.rs b/tasks/coverage/src/babel.rs index b410f32a2..85dfc3fcb 100644 --- a/tasks/coverage/src/babel.rs +++ b/tasks/coverage/src/babel.rs @@ -1,9 +1,11 @@ use std::path::{Path, PathBuf}; -use oxc_span::SourceType; use serde::{de::DeserializeOwned, Deserialize}; use serde_json::Value; +use oxc_span::SourceType; +use oxc_tasks_common::BabelOptions; + use crate::{ project_root, suite::{Case, Suite, TestResult}, @@ -17,53 +19,6 @@ pub struct BabelOutput { pub errors: Option>, } -/// options.json -#[derive(Debug, Default, Clone, Deserialize)] -#[serde(rename_all = "camelCase")] -pub struct BabelOptions { - source_type: Option, - throws: Option, - #[serde(default)] - plugins: Vec, // Can be a string or an array - #[serde(default)] - allow_return_outside_function: bool, - #[serde(default)] - allow_await_outside_function: bool, - #[serde(default)] - allow_undeclared_exports: bool, -} - -impl BabelOptions { - fn is_jsx(&self) -> bool { - self.plugins.iter().any(|v| v.as_str().is_some_and(|v| v == "jsx")) - } - - fn is_typescript(&self) -> bool { - self.plugins.iter().any(|v| { - let string_value = v.as_str().is_some_and(|v| v == "typescript"); - let array_value = v.get(0).and_then(Value::as_str).is_some_and(|s| s == "typescript"); - string_value || array_value - }) - } - - fn is_typescript_definition(&self) -> bool { - self.plugins.iter().filter_map(Value::as_array).any(|p| { - let typescript = p.get(0).and_then(Value::as_str).is_some_and(|s| s == "typescript"); - let dts = p - .get(1) - .and_then(Value::as_object) - .and_then(|v| v.get("dts")) - .and_then(Value::as_bool) - .is_some_and(|v| v); - typescript && dts - }) - } - - fn is_module(&self) -> bool { - self.source_type.as_ref().map_or(false, |s| matches!(s.as_str(), "module" | "unambiguous")) - } -} - pub struct BabelSuite { test_root: PathBuf, test_cases: Vec, @@ -147,28 +102,6 @@ impl BabelCase { Self::read_file::(&dir, "output.extended.json") } - /// read options.json, it exists in ancestor folders as well and they need to be merged - fn read_options_json(path: &Path) -> BabelOptions { - let dir = project_root().join(FIXTURES_PATH).join(path); - let mut options_json: Option = None; - for path in dir.ancestors().take(3) { - if let Some(new_json) = Self::read_file::(path, "options.json") { - if let Some(existing_json) = options_json.as_mut() { - if let Some(source_type) = new_json.source_type { - existing_json.source_type = Some(source_type); - } - if let Some(throws) = new_json.throws { - existing_json.throws = Some(throws); - } - existing_json.plugins.extend(new_json.plugins); - } else { - options_json = Some(new_json); - } - } - } - options_json.unwrap_or_default() - } - // it is an error if: // * its output.json contains an errors field // * the directory contains a options.json with a "throws" field @@ -191,7 +124,8 @@ impl BabelCase { impl Case for BabelCase { /// # Panics fn new(path: PathBuf, code: String) -> Self { - let options = Self::read_options_json(&path); + let dir = project_root().join(FIXTURES_PATH).join(&path); + let options = BabelOptions::from_path(dir.parent().unwrap()); let source_type = SourceType::from_path(&path) .unwrap() .with_script(true) diff --git a/tasks/transform_conformance/Cargo.toml b/tasks/transform_conformance/Cargo.toml index 34239df10..439f95c9a 100644 --- a/tasks/transform_conformance/Cargo.toml +++ b/tasks/transform_conformance/Cargo.toml @@ -15,6 +15,7 @@ doctest = false [dependencies] oxc_span = { workspace = true } +oxc_syntax = { workspace = true } oxc_allocator = { workspace = true } oxc_parser = { workspace = true } oxc_semantic = { workspace = true } @@ -22,8 +23,10 @@ oxc_codegen = { workspace = true } oxc_transformer = { workspace = true } oxc_tasks_common = { workspace = true } -walkdir = { workspace = true } -pico-args = { workspace = true } +serde_json = { workspace = true } +serde = { workspace = true } +walkdir = { workspace = true } +pico-args = { workspace = true } [target.'cfg(not(target_env = "msvc"))'.dependencies] jemallocator = { workspace = true } diff --git a/tasks/transform_conformance/babel.snap.md b/tasks/transform_conformance/babel.snap.md index c8fb697db..b49a37d33 100644 --- a/tasks/transform_conformance/babel.snap.md +++ b/tasks/transform_conformance/babel.snap.md @@ -1,4 +1,4 @@ -Passed: 95/1091 +Passed: 104/1091 # babel-plugin-transform-unicode-sets-regex (0/4) * Failed: basic/basic/input.js @@ -491,11 +491,8 @@ Passed: 95/1091 * Failed: to-native-fields/static-shadow/input.js * Failed: to-native-fields/static-shadowed-binding/input.js -# babel-plugin-transform-logical-assignment-operators (0/6) -* Failed: logical-assignment/anonymous-functions-transform/input.js -* Failed: logical-assignment/arrow-functions-transform/input.js +# babel-plugin-transform-logical-assignment-operators (3/6) * Failed: logical-assignment/general-semantics/input.js -* Failed: logical-assignment/named-functions-transform/input.js * Failed: logical-assignment/null-coalescing/input.js * Failed: logical-assignment/null-coalescing-without-other/input.js @@ -535,15 +532,9 @@ Passed: 95/1091 * Failed: export-namespace/namespace-string/input.mjs * Failed: export-namespace/namespace-typescript/input.mjs -# babel-plugin-transform-nullish-coalescing-operator (4/12) -* Failed: assumption-noDocumentAll/transform/input.js -* Failed: assumption-noDocumentAll/transform-in-default-destructuring/input.js +# babel-plugin-transform-nullish-coalescing-operator (10/12) * Failed: assumption-noDocumentAll/transform-in-default-param/input.js -* Failed: assumption-noDocumentAll/transform-in-function/input.js -* Failed: assumption-noDocumentAll/transform-static-refs-in-default/input.js -* Failed: assumption-noDocumentAll/transform-static-refs-in-function/input.js * Failed: nullish-coalescing/transform-in-default-param/input.js -* Failed: nullish-coalescing/transform-loose/input.js # babel-plugin-transform-optional-chaining (1/46) * Failed: assumption-noDocumentAll/assignment/input.js diff --git a/tasks/transform_conformance/src/lib.rs b/tasks/transform_conformance/src/lib.rs index 28aee50e4..d1a4a14fe 100644 --- a/tasks/transform_conformance/src/lib.rs +++ b/tasks/transform_conformance/src/lib.rs @@ -1,8 +1,10 @@ +use serde::de::DeserializeOwned; +use serde_json::Value; use std::{ cell::RefCell, fs::{self, File}, io::Write, - path::{Path, PathBuf}, + path::PathBuf, rc::Rc, }; use walkdir::WalkDir; @@ -12,9 +14,10 @@ use oxc_codegen::{Codegen, CodegenOptions}; use oxc_parser::Parser; use oxc_semantic::SemanticBuilder; use oxc_span::{SourceType, VALID_EXTENSIONS}; -use oxc_tasks_common::{normalize_path, project_root}; +use oxc_tasks_common::{normalize_path, project_root, BabelOptions}; use oxc_transformer::{ - Assumptions, TransformOptions, TransformReactOptions, TransformTarget, Transformer, + NullishCoalescingOperatorOptions, ReactJsxOptions, TransformOptions, TransformTarget, + Transformer, }; #[test] @@ -24,163 +27,216 @@ fn test() { } #[derive(Default)] -pub struct BabelOptions { +pub struct TestRunnerOptions { pub filter: Option, } -/// # Panics -pub fn babel(options: &BabelOptions) { - let root = project_root().join("tasks/coverage/babel/packages"); +/// The test runner which walks the babel repository and searches for transformation tests. +pub struct TestRunner { + options: TestRunnerOptions, +} - let cases = [ - // ES2024 - "babel-plugin-transform-unicode-sets-regex", - // ES2022 - "babel-plugin-transform-class-properties", - "babel-plugin-transform-class-static-block", - "babel-plugin-transform-private-methods", - "babel-plugin-transform-private-property-in-object", - // [Syntax] "babel-plugin-transform-syntax-top-level-await", - // ES2021 - "babel-plugin-transform-logical-assignment-operators", - "babel-plugin-transform-numeric-separator", - // ES2020 - "babel-plugin-transform-export-namespace-from", - "babel-plugin-transform-dynamic-import", - "babel-plugin-transform-export-namespace-from", - "babel-plugin-transform-nullish-coalescing-operator", - "babel-plugin-transform-optional-chaining", - // [Syntax] "babel-plugin-transform-syntax-bigint", - // [Syntax] "babel-plugin-transform-syntax-dynamic-import", - // [Syntax] "babel-plugin-transform-syntax-import-meta", - // ES2019 - "babel-plugin-transform-optional-catch-binding", - "babel-plugin-transform-json-strings", - // ES2018 - "babel-plugin-transform-async-generator-functions", - "babel-plugin-transform-object-rest-spread", - // [Regex] "babel-plugin-transform-unicode-property-regex", - "babel-plugin-transform-dotall-regex", - // [Regex] "babel-plugin-transform-named-capturing-groups-regex", - // ES2017 - "babel-plugin-transform-async-to-generator", - // ES2016 - "babel-plugin-transform-exponentiation-operator", - // ES2015 - "babel-plugin-transform-shorthand-properties", - "babel-plugin-transform-sticky-regex", - "babel-plugin-transform-unicode-regex", - // TypeScript - "babel-plugin-transform-typescript", - // React - "babel-plugin-transform-react-jsx", - ]; +fn root() -> PathBuf { + project_root().join("tasks/coverage/babel/packages") +} - let mut snapshot = String::new(); - let mut total = 0; - let mut all_passed = 0; +impl TestRunner { + pub fn new(options: TestRunnerOptions) -> Self { + Self { options } + } - // Get all fixtures - for case in cases { - let root = root.join(case).join("test/fixtures"); - let mut paths = WalkDir::new(&root) - .into_iter() - .filter_map(Result::ok) - .filter(|e| { - e.path().file_stem().is_some_and(|name| name == "input") - && e.path() - .extension() - .is_some_and(|ext| VALID_EXTENSIONS.contains(&ext.to_str().unwrap())) - }) - .map(walkdir::DirEntry::into_path) - .collect::>(); - paths.sort_unstable(); - let num_of_tests = paths.len(); - total += num_of_tests; + /// # Panics + pub fn run(self) { + let root = root(); - // Run the test - let (passed, failed): (Vec, Vec) = - paths.into_iter().partition(|path| babel_test(path, options)); - all_passed += passed.len(); + let cases = [ + // ES2024 + "babel-plugin-transform-unicode-sets-regex", + // ES2022 + "babel-plugin-transform-class-properties", + "babel-plugin-transform-class-static-block", + "babel-plugin-transform-private-methods", + "babel-plugin-transform-private-property-in-object", + // [Syntax] "babel-plugin-transform-syntax-top-level-await", + // ES2021 + "babel-plugin-transform-logical-assignment-operators", + "babel-plugin-transform-numeric-separator", + // ES2020 + "babel-plugin-transform-export-namespace-from", + "babel-plugin-transform-dynamic-import", + "babel-plugin-transform-export-namespace-from", + "babel-plugin-transform-nullish-coalescing-operator", + "babel-plugin-transform-optional-chaining", + // [Syntax] "babel-plugin-transform-syntax-bigint", + // [Syntax] "babel-plugin-transform-syntax-dynamic-import", + // [Syntax] "babel-plugin-transform-syntax-import-meta", + // ES2019 + "babel-plugin-transform-optional-catch-binding", + "babel-plugin-transform-json-strings", + // ES2018 + "babel-plugin-transform-async-generator-functions", + "babel-plugin-transform-object-rest-spread", + // [Regex] "babel-plugin-transform-unicode-property-regex", + "babel-plugin-transform-dotall-regex", + // [Regex] "babel-plugin-transform-named-capturing-groups-regex", + // ES2017 + "babel-plugin-transform-async-to-generator", + // ES2016 + "babel-plugin-transform-exponentiation-operator", + // ES2015 + "babel-plugin-transform-shorthand-properties", + "babel-plugin-transform-sticky-regex", + "babel-plugin-transform-unicode-regex", + // TypeScript + "babel-plugin-transform-typescript", + // React + "babel-plugin-transform-react-jsx", + ]; - // Snapshot - snapshot.push_str("# "); - snapshot.push_str(case); - if failed.is_empty() { - snapshot.push_str(" (All passed)\n"); - } else { - snapshot.push_str(&format!(" ({}/{})\n", passed.len(), num_of_tests)); - } - for path in failed { - snapshot.push_str("* Failed: "); - snapshot.push_str(&normalize_path(path.strip_prefix(&root).unwrap())); + let mut snapshot = String::new(); + let mut total = 0; + let mut all_passed = 0; + + // Get all fixtures + for case in cases { + let root = root.join(case).join("test/fixtures"); + let mut paths = WalkDir::new(&root) + .into_iter() + .filter_map(Result::ok) + .filter(|e| { + e.path().file_stem().is_some_and(|name| name == "input") + && e.path() + .extension() + .is_some_and(|ext| VALID_EXTENSIONS.contains(&ext.to_str().unwrap())) + }) + .map(walkdir::DirEntry::into_path) + .collect::>(); + paths.sort_unstable(); + let num_of_tests = paths.len(); + total += num_of_tests; + + // Run the test + let (passed, failed): (Vec, Vec) = paths + .into_iter() + .partition(|path| TestCase::new(path).test(self.options.filter.as_deref())); + all_passed += passed.len(); + + // Snapshot + snapshot.push_str("# "); + snapshot.push_str(case); + if failed.is_empty() { + snapshot.push_str(" (All passed)\n"); + } else { + snapshot.push_str(&format!(" ({}/{})\n", passed.len(), num_of_tests)); + } + for path in failed { + snapshot.push_str("* Failed: "); + snapshot.push_str(&normalize_path(path.strip_prefix(&root).unwrap())); + snapshot.push('\n'); + } snapshot.push('\n'); } - snapshot.push('\n'); - } - let snapshot = format!("Passed: {all_passed}/{total}\n\n{snapshot}"); - let path = project_root().join("tasks/transform_conformance/babel.snap.md"); - let mut file = File::create(path).unwrap(); - file.write_all(snapshot.as_bytes()).unwrap(); + let snapshot = format!("Passed: {all_passed}/{total}\n\n{snapshot}"); + let path = project_root().join("tasks/transform_conformance/babel.snap.md"); + let mut file = File::create(path).unwrap(); + file.write_all(snapshot.as_bytes()).unwrap(); + } } -/// Test conformance by comparing the parsed babel code and transformed code. -fn babel_test(input_path: &Path, options: &BabelOptions) -> bool { - let output_path = input_path.parent().unwrap().read_dir().unwrap().find_map(|entry| { - let path = entry.ok()?.path(); - let file_stem = path.file_stem()?; - (file_stem == "output").then_some(path) - }); - let source_text = fs::read_to_string(input_path).unwrap(); - let filtered = - options.filter.as_ref().is_some_and(|f| input_path.to_string_lossy().as_ref().contains(f)); - - if filtered { - println!("input_path: {input_path:?}"); - println!("output_path: {output_path:?}"); - } - - let allocator = Allocator::default(); - let source_type = SourceType::from_path(input_path).unwrap(); - - // Get expected code by parsing the source text, so we can get the same code generated result. - let expected = output_path.and_then(|path| fs::read_to_string(path).ok()); - let Some(expected) = &expected else { return false }; - let expected_program = Parser::new(&allocator, expected, source_type).parse().program; - let expected_code = - Codegen::::new(source_text.len(), CodegenOptions).build(&expected_program); - - // Get transformed text. - - let transformed_program = Parser::new(&allocator, &source_text, source_type).parse().program; - let transform_options = TransformOptions { - target: TransformTarget::ES5, - react: Some(TransformReactOptions::default()), - assumptions: Assumptions::default(), - }; - - let semantic = - SemanticBuilder::new(&source_text, source_type).build(&transformed_program).semantic; - let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree(); - let symbols = Rc::new(RefCell::new(symbols)); - - let transformed_program = allocator.alloc(transformed_program); - - Transformer::new(&allocator, source_type, &symbols, transform_options) - .build(transformed_program); - let transformed_code = - Codegen::::new(source_text.len(), CodegenOptions).build(transformed_program); - - let passed = transformed_code == expected_code; - if filtered { - println!("Expected:\n"); - println!("{expected}\n"); - println!("Expected codegen:\n"); - println!("{expected_code}\n"); - println!("Transformed:\n"); - println!("{transformed_code}\n"); - println!("Passed: {passed}"); - } - passed +struct TestCase { + path: PathBuf, + options: BabelOptions, +} + +impl TestCase { + fn new>(path: P) -> Self { + let path = path.into(); + let options = BabelOptions::from_path(path.parent().unwrap()); + Self { path, options } + } + + fn transform_options(&self) -> TransformOptions { + fn get_options(value: Option) -> T { + value.and_then(|v| serde_json::from_value::(v).ok()).unwrap_or_default() + } + + let options = &self.options; + TransformOptions { + target: TransformTarget::ESNext, + react_jsx: Some(ReactJsxOptions::default()), + assumptions: options.assumptions, + class_static_block: options.get_plugin("transform-class-static-block").is_some(), + logical_assignment_operators: options + .get_plugin("transform-logical-assignment-operators") + .is_some(), + nullish_coalescing_operator: self + .options + .get_plugin("transform-nullish-coalescing-operator") + .map(get_options::), + optional_catch_binding: options + .get_plugin("transform-optional-catch-binding") + .is_some(), + exponentiation_operator: options + .get_plugin("transform-exponentiation-operator") + .is_some(), + shorthand_properties: options.get_plugin("transform-shorthand-properties").is_some(), + sticky_regex: options.get_plugin("transform-sticky-regex").is_some(), + } + } + + /// Test conformance by comparing the parsed babel code and transformed code. + fn test(&self, filter: Option<&str>) -> bool { + let output_path = self.path.parent().unwrap().read_dir().unwrap().find_map(|entry| { + let path = entry.ok()?.path(); + let file_stem = path.file_stem()?; + (file_stem == "output").then_some(path) + }); + let source_text = fs::read_to_string(&self.path).unwrap(); + let filtered = filter.is_some_and(|f| self.path.to_string_lossy().as_ref().contains(f)); + + if filtered { + println!("input_path: {:?}", &self.path); + println!("output_path: {output_path:?}"); + } + + let allocator = Allocator::default(); + let source_type = SourceType::from_path(&self.path).unwrap(); + + // Get expected code by parsing the source text, so we can get the same code generated result. + let expected = output_path.and_then(|path| fs::read_to_string(path).ok()); + let Some(expected) = &expected else { return false }; + let expected_program = Parser::new(&allocator, expected, source_type).parse().program; + let expected_code = + Codegen::::new(source_text.len(), CodegenOptions).build(&expected_program); + + // Get transformed text. + + let transformed_program = + Parser::new(&allocator, &source_text, source_type).parse().program; + + let semantic = + SemanticBuilder::new(&source_text, source_type).build(&transformed_program).semantic; + let (symbols, _scope_tree) = semantic.into_symbol_table_and_scope_tree(); + let symbols = Rc::new(RefCell::new(symbols)); + + let transformed_program = allocator.alloc(transformed_program); + + Transformer::new(&allocator, source_type, &symbols, self.transform_options()) + .build(transformed_program); + let transformed_code = + Codegen::::new(source_text.len(), CodegenOptions).build(transformed_program); + + let passed = transformed_code == expected_code; + if filtered { + println!("Expected:\n"); + println!("{expected}\n"); + println!("Expected codegen:\n"); + println!("{expected_code}\n"); + println!("Transformed:\n"); + println!("{transformed_code}\n"); + println!("Passed: {passed}"); + } + passed + } } diff --git a/tasks/transform_conformance/src/main.rs b/tasks/transform_conformance/src/main.rs index 874e7fafb..3cc7ea411 100644 --- a/tasks/transform_conformance/src/main.rs +++ b/tasks/transform_conformance/src/main.rs @@ -6,13 +6,13 @@ static GLOBAL: jemallocator::Jemalloc = jemallocator::Jemalloc; #[global_allocator] static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc; -use oxc_transform_conformance::{babel, BabelOptions}; +use oxc_transform_conformance::{TestRunner, TestRunnerOptions}; use pico_args::Arguments; fn main() { let mut args = Arguments::from_env(); - let options = BabelOptions { filter: args.opt_value_from_str("--filter").unwrap() }; + let options = TestRunnerOptions { filter: args.opt_value_from_str("--filter").unwrap() }; - babel(&options); + TestRunner::new(options).run(); }