mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
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
This commit is contained in:
parent
077585addc
commit
1b3b100475
26 changed files with 497 additions and 346 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
|
@ -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]]
|
||||
|
|
|
|||
|
|
@ -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"]
|
||||
|
|
|
|||
15
crates/oxc_syntax/src/assumptions.rs
Normal file
15
crates/oxc_syntax/src/assumptions.rs
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#[cfg(feature = "serde")]
|
||||
use serde::Deserialize;
|
||||
|
||||
/// Compiler assumptions
|
||||
///
|
||||
/// See <https://babeljs.io/docs/assumptions>
|
||||
#[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 <https://babeljs.io/docs/assumptions#nodocumentall>.
|
||||
#[cfg_attr(feature = "serde", serde(default))]
|
||||
pub no_document_all: bool,
|
||||
}
|
||||
|
|
@ -1,5 +1,6 @@
|
|||
//! Common code for JavaScript Syntax
|
||||
|
||||
pub mod assumptions;
|
||||
pub mod identifier;
|
||||
pub mod module_record;
|
||||
pub mod operator;
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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::<false>::new(source_text.len(), codegen_options).build(program);
|
||||
println!("Transformed:\n");
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||
(options.target < TransformTarget::ES2015 || options.shorthand_properties)
|
||||
.then(|| Self { ast })
|
||||
}
|
||||
|
||||
pub fn transform_object_property<'b>(&mut self, obj_prop: &'b mut ObjectProperty<'a>) {
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>, symbols: Rc<RefCell<SymbolTable>>) -> Self {
|
||||
let vars = ast.new_vec();
|
||||
Self { ast, symbols, vars }
|
||||
pub fn new(
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
symbols: Rc<RefCell<SymbolTable>>,
|
||||
options: &TransformOptions,
|
||||
) -> Option<Self> {
|
||||
(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>) {
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||
(options.target < TransformTarget::ES2019 || options.optional_catch_binding)
|
||||
.then(|| Self { ast })
|
||||
}
|
||||
|
||||
pub fn transform_catch_clause<'b>(&mut self, clause: &'b mut CatchClause<'a>) {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
mod nullish_coalescing_operator;
|
||||
|
||||
pub use nullish_coalescing_operator::NullishCoalescingOperator;
|
||||
pub use nullish_coalescing_operator::{
|
||||
NullishCoalescingOperator, NullishCoalescingOperatorOptions,
|
||||
};
|
||||
|
|
|
|||
|
|
@ -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};
|
|||
/// * <https://babeljs.io/docs/babel-plugin-transform-nullish-coalescing-operator>
|
||||
/// * <https://github.com/babel/babel/tree/main/packages/babel-plugin-transform-nullish-coalescing-operator>
|
||||
pub struct NullishCoalescingOperator<'a> {
|
||||
no_document_all: bool,
|
||||
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
symbols: Rc<RefCell<SymbolTable>>,
|
||||
assumptions: Assumptions,
|
||||
vars: Vec<'a, VariableDeclarator<'a>>,
|
||||
}
|
||||
|
||||
|
|
@ -34,10 +44,15 @@ impl<'a> NullishCoalescingOperator<'a> {
|
|||
pub fn new(
|
||||
ast: Rc<AstBuilder<'a>>,
|
||||
symbols: Rc<RefCell<SymbolTable>>,
|
||||
assumptions: Assumptions,
|
||||
) -> Self {
|
||||
let vars = ast.new_vec();
|
||||
Self { ast, symbols, assumptions, vars }
|
||||
options: &TransformOptions,
|
||||
) -> Option<Self> {
|
||||
(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 {
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||
(options.target < TransformTarget::ES2021 || options.logical_assignment_operators)
|
||||
.then(|| Self { ast })
|
||||
}
|
||||
|
||||
pub fn transform_expression<'b>(&mut self, expr: &'b mut Expression<'a>) {
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>) -> Self {
|
||||
Self { ast }
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||
(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>) {
|
||||
|
|
|
|||
|
|
@ -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<ReactJsx<'a>>,
|
||||
regexp_flags: Option<RegexpFlags<'a>>,
|
||||
// es2022
|
||||
es2022_class_static_block: Option<es2022::ClassStaticBlock<'a>>,
|
||||
es2022_class_static_block: Option<ClassStaticBlock<'a>>,
|
||||
// es2021
|
||||
es2021_logical_assignment_operators: Option<LogicalAssignmentOperators<'a>>,
|
||||
// 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),
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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<TransformReactOptions>,
|
||||
pub assumptions: Assumptions,
|
||||
pub assumptions: CompilerAssumptions,
|
||||
|
||||
pub react_jsx: Option<ReactJsxOptions>,
|
||||
|
||||
// es2022
|
||||
pub class_static_block: bool,
|
||||
// es2021
|
||||
pub logical_assignment_operators: bool,
|
||||
// es2020
|
||||
pub nullish_coalescing_operator: Option<NullishCoalescingOperatorOptions>,
|
||||
// es2019
|
||||
pub optional_catch_binding: bool,
|
||||
// es2016
|
||||
pub exponentiation_operator: bool,
|
||||
// es2015
|
||||
pub shorthand_properties: bool,
|
||||
pub sticky_regex: bool,
|
||||
}
|
||||
|
||||
/// See <https://www.typescriptlang.org/tsconfig#target>
|
||||
|
|
@ -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 <https://babeljs.io/docs/assumptions>
|
||||
#[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 <https://babeljs.io/docs/assumptions#nodocumentall>.
|
||||
pub no_document_all: bool,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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;
|
|||
/// * <https://github.com/babel/babel/tree/main/packages/babel-helper-builder-react-jsx>
|
||||
pub struct ReactJsx<'a> {
|
||||
_ast: Rc<AstBuilder<'a>>,
|
||||
_options: TransformReactOptions,
|
||||
_options: ReactJsxOptions,
|
||||
}
|
||||
|
||||
impl<'a> ReactJsx<'a> {
|
||||
pub fn new(_ast: Rc<AstBuilder<'a>>, _options: TransformReactOptions) -> Self {
|
||||
pub fn new(_ast: Rc<AstBuilder<'a>>, _options: ReactJsxOptions) -> Self {
|
||||
Self { _ast, _options }
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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<AstBuilder<'a>>, transform_target: TransformTarget) -> Option<Self> {
|
||||
let transform_flags = Self::from_transform_target(transform_target);
|
||||
pub fn new(ast: Rc<AstBuilder<'a>>, options: &TransformOptions) -> Option<Self> {
|
||||
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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
94
tasks/common/src/babel.rs
Normal file
94
tasks/common/src/babel.rs
Normal file
|
|
@ -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<String>,
|
||||
pub throws: Option<String>,
|
||||
#[serde(default)]
|
||||
pub plugins: Vec<Value>, // 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<Self> = 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<None>` if the plugin exists without a config
|
||||
/// * `Some<Some<Value>>` if the plugin exists with a config
|
||||
/// * `None` if the plugin does not exist
|
||||
pub fn get_plugin(&self, name: &str) -> Option<Option<Value>> {
|
||||
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,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<Vec<String>>,
|
||||
}
|
||||
|
||||
/// options.json
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
pub struct BabelOptions {
|
||||
source_type: Option<String>,
|
||||
throws: Option<String>,
|
||||
#[serde(default)]
|
||||
plugins: Vec<Value>, // 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<T: Case> {
|
||||
test_root: PathBuf,
|
||||
test_cases: Vec<T>,
|
||||
|
|
@ -147,28 +102,6 @@ impl BabelCase {
|
|||
Self::read_file::<BabelOutput>(&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<BabelOptions> = None;
|
||||
for path in dir.ancestors().take(3) {
|
||||
if let Some(new_json) = Self::read_file::<BabelOptions>(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)
|
||||
|
|
|
|||
|
|
@ -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 }
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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<String>,
|
||||
}
|
||||
|
||||
/// # 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::<Vec<_>>();
|
||||
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<PathBuf>, Vec<PathBuf>) =
|
||||
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::<Vec<_>>();
|
||||
paths.sort_unstable();
|
||||
let num_of_tests = paths.len();
|
||||
total += num_of_tests;
|
||||
|
||||
// Run the test
|
||||
let (passed, failed): (Vec<PathBuf>, Vec<PathBuf>) = 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::<false>::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::<false>::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<P: Into<PathBuf>>(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<T: Default + DeserializeOwned>(value: Option<Value>) -> T {
|
||||
value.and_then(|v| serde_json::from_value::<T>(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::<NullishCoalescingOperatorOptions>),
|
||||
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::<false>::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::<false>::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
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in a new issue