mirror of
https://github.com/danbulant/oxc
synced 2026-05-19 04:08:41 +00:00
parent
64e24994e6
commit
21b8e4988f
13 changed files with 256 additions and 46 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
|
@ -2022,6 +2022,7 @@ dependencies = [
|
|||
"cow-utils",
|
||||
"dashmap 6.1.0",
|
||||
"indexmap",
|
||||
"insta",
|
||||
"itoa",
|
||||
"oxc-browserslist",
|
||||
"oxc_allocator",
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ serde_json = { workspace = true }
|
|||
sha1 = { workspace = true }
|
||||
|
||||
[dev-dependencies]
|
||||
insta = { workspace = true }
|
||||
oxc_codegen = { workspace = true }
|
||||
oxc_parser = { workspace = true }
|
||||
pico-args = { workspace = true }
|
||||
|
|
|
|||
|
|
@ -1,26 +1,27 @@
|
|||
#![allow(clippy::print_stdout)]
|
||||
use std::{env, path::Path};
|
||||
use std::{path::Path, str::FromStr};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::CodeGenerator;
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{TransformOptions, Transformer};
|
||||
use oxc_transformer::{ESTarget, TransformOptions, Transformer};
|
||||
use pico_args::Arguments;
|
||||
|
||||
// Instruction:
|
||||
// create a `test.tsx`,
|
||||
// run `cargo run -p oxc_transformer --example transformer`
|
||||
// or `just watch "run -p oxc_transformer --example transformer"`
|
||||
// create a `test.js`,
|
||||
// run `just example transformer` or `just watch-example transformer`
|
||||
|
||||
fn main() {
|
||||
let mut args = Arguments::from_env();
|
||||
let name = env::args().nth(1).unwrap_or_else(|| "test.js".to_string());
|
||||
let targets: Option<String> = args.opt_value_from_str("--targets").unwrap_or(None);
|
||||
let target: Option<String> = args.opt_value_from_str("--target").unwrap_or(None);
|
||||
let name = args.free_from_str().unwrap_or_else(|_| "test.js".to_string());
|
||||
|
||||
let path = Path::new(&name);
|
||||
let source_text = std::fs::read_to_string(path).expect("{name} not found");
|
||||
let source_text =
|
||||
std::fs::read_to_string(path).unwrap_or_else(|err| panic!("{name} not found.\n{err}"));
|
||||
let allocator = Allocator::default();
|
||||
let source_type = SourceType::from_path(path).unwrap();
|
||||
|
||||
|
|
@ -62,6 +63,8 @@ fn main() {
|
|||
// ..BabelEnvOptions::default()
|
||||
// })
|
||||
// .unwrap()
|
||||
} else if let Some(target) = &target {
|
||||
TransformOptions::from(ESTarget::from_str(target).unwrap())
|
||||
} else {
|
||||
TransformOptions::enable_all()
|
||||
};
|
||||
|
|
|
|||
|
|
@ -56,7 +56,7 @@ pub use crate::{
|
|||
jsx::{JsxOptions, JsxRuntime, ReactRefreshOptions},
|
||||
options::{
|
||||
babel::{BabelEnvOptions, BabelOptions, Targets},
|
||||
EnvOptions, TransformOptions,
|
||||
ESTarget, EnvOptions, TransformOptions,
|
||||
},
|
||||
plugins::*,
|
||||
typescript::{RewriteExtensionsMode, TypeScriptOptions},
|
||||
|
|
|
|||
|
|
@ -1,3 +1,6 @@
|
|||
use std::str::FromStr;
|
||||
|
||||
use cow_utils::CowUtils;
|
||||
use serde::Deserialize;
|
||||
|
||||
use crate::{
|
||||
|
|
@ -14,6 +17,45 @@ use crate::{
|
|||
|
||||
use super::babel::BabelEnvOptions;
|
||||
|
||||
#[derive(Debug, Default, Clone, Copy, Eq, PartialEq, Ord, PartialOrd)]
|
||||
pub enum ESTarget {
|
||||
ES5,
|
||||
ES2015,
|
||||
ES2016,
|
||||
ES2017,
|
||||
ES2018,
|
||||
ES2019,
|
||||
ES2020,
|
||||
ES2021,
|
||||
ES2022,
|
||||
ES2023,
|
||||
ES2024,
|
||||
#[default]
|
||||
ESNext,
|
||||
}
|
||||
|
||||
impl FromStr for ESTarget {
|
||||
type Err = String;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
match s.cow_to_lowercase().as_ref() {
|
||||
"es5" => Ok(Self::ES5),
|
||||
"es2015" => Ok(Self::ES2015),
|
||||
"es2016" => Ok(Self::ES2016),
|
||||
"es2017" => Ok(Self::ES2017),
|
||||
"es2018" => Ok(Self::ES2018),
|
||||
"es2019" => Ok(Self::ES2019),
|
||||
"es2020" => Ok(Self::ES2020),
|
||||
"es2021" => Ok(Self::ES2021),
|
||||
"es2022" => Ok(Self::ES2022),
|
||||
"es2023" => Ok(Self::ES2023),
|
||||
"es2024" => Ok(Self::ES2024),
|
||||
"esnext" => Ok(Self::ESNext),
|
||||
_ => Err(format!("Invalid target \"{s}\".")),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug, Default, Clone, Deserialize)]
|
||||
#[serde(try_from = "BabelEnvOptions")]
|
||||
pub struct EnvOptions {
|
||||
|
|
@ -46,10 +88,10 @@ impl EnvOptions {
|
|||
regexp: RegExpOptions {
|
||||
sticky_flag: true,
|
||||
unicode_flag: true,
|
||||
dot_all_flag: true,
|
||||
look_behind_assertions: true,
|
||||
named_capture_groups: true,
|
||||
unicode_property_escapes: true,
|
||||
dot_all_flag: true,
|
||||
named_capture_groups: true,
|
||||
look_behind_assertions: true,
|
||||
match_indices: true,
|
||||
set_notation: true,
|
||||
},
|
||||
|
|
@ -91,6 +133,40 @@ impl EnvOptions {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ESTarget> for EnvOptions {
|
||||
fn from(target: ESTarget) -> Self {
|
||||
Self {
|
||||
regexp: RegExpOptions {
|
||||
sticky_flag: target < ESTarget::ES2015,
|
||||
unicode_flag: target < ESTarget::ES2015,
|
||||
unicode_property_escapes: target < ESTarget::ES2018,
|
||||
dot_all_flag: target < ESTarget::ES2015,
|
||||
named_capture_groups: target < ESTarget::ES2018,
|
||||
look_behind_assertions: target < ESTarget::ES2018,
|
||||
match_indices: target < ESTarget::ES2022,
|
||||
set_notation: target < ESTarget::ES2024,
|
||||
},
|
||||
es2015: ES2015Options {
|
||||
arrow_function: (target < ESTarget::ES2015).then(ArrowFunctionsOptions::default),
|
||||
},
|
||||
es2016: ES2016Options { exponentiation_operator: target < ESTarget::ES2016 },
|
||||
es2017: ES2017Options { async_to_generator: target < ESTarget::ES2017 },
|
||||
es2018: ES2018Options {
|
||||
object_rest_spread: (target < ESTarget::ES2018)
|
||||
.then(ObjectRestSpreadOptions::default),
|
||||
async_generator_functions: target < ESTarget::ES2018,
|
||||
},
|
||||
es2019: ES2019Options { optional_catch_binding: target < ESTarget::ES2019 },
|
||||
es2020: ES2020Options { nullish_coalescing_operator: target < ESTarget::ES2020 },
|
||||
es2021: ES2021Options { logical_assignment_operators: target < ESTarget::ES2021 },
|
||||
es2022: ES2022Options {
|
||||
class_static_block: target < ESTarget::ES2022,
|
||||
class_properties: (target < ESTarget::ES2022).then(ClassPropertiesOptions::default),
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<BabelEnvOptions> for EnvOptions {
|
||||
type Error = String;
|
||||
|
||||
|
|
@ -100,10 +176,10 @@ impl TryFrom<BabelEnvOptions> for EnvOptions {
|
|||
regexp: RegExpOptions {
|
||||
sticky_flag: o.can_enable_plugin("transform-sticky-regex"),
|
||||
unicode_flag: o.can_enable_plugin("transform-unicode-regex"),
|
||||
dot_all_flag: o.can_enable_plugin("transform-dotall-regex"),
|
||||
look_behind_assertions: o.can_enable_plugin("esbuild-regexp-lookbehind-assertions"),
|
||||
named_capture_groups: o.can_enable_plugin("transform-named-capturing-groups-regex"),
|
||||
unicode_property_escapes: o.can_enable_plugin("transform-unicode-property-regex"),
|
||||
dot_all_flag: o.can_enable_plugin("transform-dotall-regex"),
|
||||
named_capture_groups: o.can_enable_plugin("transform-named-capturing-groups-regex"),
|
||||
look_behind_assertions: o.can_enable_plugin("esbuild-regexp-lookbehind-assertions"),
|
||||
match_indices: o.can_enable_plugin("esbuild-regexp-match-indices"),
|
||||
set_notation: o.can_enable_plugin("transform-unicode-sets-regex"),
|
||||
},
|
||||
|
|
|
|||
|
|
@ -5,8 +5,6 @@ use std::path::PathBuf;
|
|||
|
||||
use oxc_diagnostics::Error;
|
||||
|
||||
pub use env::EnvOptions;
|
||||
|
||||
use crate::{
|
||||
common::helper_loader::{HelperLoaderMode, HelperLoaderOptions},
|
||||
compiler_assumptions::CompilerAssumptions,
|
||||
|
|
@ -24,6 +22,8 @@ use crate::{
|
|||
ReactRefreshOptions,
|
||||
};
|
||||
|
||||
pub use env::{ESTarget, EnvOptions};
|
||||
|
||||
use babel::BabelOptions;
|
||||
|
||||
/// <https://babel.dev/docs/options>
|
||||
|
|
@ -79,6 +79,12 @@ impl TransformOptions {
|
|||
}
|
||||
}
|
||||
|
||||
impl From<ESTarget> for TransformOptions {
|
||||
fn from(target: ESTarget) -> Self {
|
||||
Self { env: EnvOptions::from(target), ..Self::default() }
|
||||
}
|
||||
}
|
||||
|
||||
impl TryFrom<&BabelOptions> for TransformOptions {
|
||||
type Error = Vec<Error>;
|
||||
|
||||
|
|
|
|||
|
|
@ -1,26 +1,34 @@
|
|||
#[derive(Default, Debug, Clone, Copy)]
|
||||
pub struct RegExpOptions {
|
||||
/// Enables plugin to transform the RegExp literal has `y` flag
|
||||
/// Enables plugin to transform the RegExp literal with a `y` flag
|
||||
/// ES2015 <https://babel.dev/docs/babel-plugin-transform-sticky-regex>
|
||||
pub sticky_flag: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `u` flag
|
||||
/// Enables plugin to transform the RegExp literal with a `u` flag
|
||||
/// ES2015 <https://babel.dev/docs/babel-plugin-transform-unicode-regex>
|
||||
pub unicode_flag: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `s` flag
|
||||
pub dot_all_flag: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `(?<=)` or `(?<!)` lookbehind assertions
|
||||
pub look_behind_assertions: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `(?<name>x)` named capture groups
|
||||
pub named_capture_groups: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `\p{}` and `\P{}` unicode property escapes
|
||||
/// Enables plugin to transform the RegExp literal that has `\p{}` and `\P{}` unicode property escapes
|
||||
/// ES2018 <https://babel.dev/docs/babel-plugin-transform-unicode-property-regex>
|
||||
pub unicode_property_escapes: bool,
|
||||
|
||||
/// Enables plugin to transform `d` flag
|
||||
/// Enables plugin to transform the RegExp literal with a `s` flag
|
||||
/// ES2018 <https://babel.dev/docs/babel-plugin-transform-dotall-regex>
|
||||
pub dot_all_flag: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal that has `(?<name>x)` named capture groups
|
||||
/// ES2018 <https://babel.dev/docs/babel-plugin-transform-named-capturing-groups-regex>
|
||||
pub named_capture_groups: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal that has `(?<=)` or `(?<!)` lookbehind assertions
|
||||
/// ES2018 <https://github.com/tc39/proposal-regexp-lookbehind>
|
||||
pub look_behind_assertions: bool,
|
||||
|
||||
/// Enables plugin to transform the `d` flag
|
||||
/// ES2022 <https://github.com/tc39/proposal-regexp-match-indices>
|
||||
pub match_indices: bool,
|
||||
|
||||
/// Enables plugin to transform the RegExp literal has `v` flag
|
||||
/// Enables plugin to transform the RegExp literal that has `v` flag
|
||||
/// ES2024 <https://babel.dev/docs/babel-plugin-transform-unicode-sets-regex>
|
||||
pub set_notation: bool,
|
||||
}
|
||||
|
|
|
|||
60
crates/oxc_transformer/tests/es_target/mod.rs
Normal file
60
crates/oxc_transformer/tests/es_target/mod.rs
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
use std::{path::Path, str::FromStr};
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{CodeGenerator, CodegenOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_semantic::SemanticBuilder;
|
||||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{ESTarget, TransformOptions, Transformer};
|
||||
|
||||
use crate::run;
|
||||
|
||||
pub(crate) fn test(source_text: &str, target: &str) -> String {
|
||||
let source_type = SourceType::default();
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
let mut program = ret.program;
|
||||
let (symbols, scopes) =
|
||||
SemanticBuilder::new().build(&program).semantic.into_symbol_table_and_scope_tree();
|
||||
let options = TransformOptions::from(ESTarget::from_str(target).unwrap());
|
||||
Transformer::new(&allocator, Path::new(""), options).build_with_symbols_and_scopes(
|
||||
symbols,
|
||||
scopes,
|
||||
&mut program,
|
||||
);
|
||||
CodeGenerator::new()
|
||||
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
||||
.build(&program)
|
||||
.code
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn es2015() {
|
||||
use std::fmt::Write;
|
||||
|
||||
let cases = [
|
||||
("es5", "() => {}"),
|
||||
("es2015", "a ** b"),
|
||||
("es2016", "async function foo() {}"),
|
||||
("es2017", "({ ...x })"),
|
||||
("es2018", "try {} catch {}"),
|
||||
("es2019", "a ?? b"),
|
||||
("es2020", "a ||= b"),
|
||||
("es2021", "class foo { static {} }"),
|
||||
];
|
||||
|
||||
// Test no transformation for esnext.
|
||||
for (_, case) in cases {
|
||||
assert_eq!(run(case, SourceType::mjs()), test(case, "esnext"));
|
||||
}
|
||||
|
||||
let snapshot = cases.iter().enumerate().fold(String::new(), |mut w, (i, (target, case))| {
|
||||
let result = test(case, target);
|
||||
write!(w, "########## {i} {target}\n{case}\n----------\n{result}\n").unwrap();
|
||||
w
|
||||
});
|
||||
|
||||
insta::with_settings!({ prepend_module_to_snapshot => false, snapshot_suffix => "", omit_expression => true }, {
|
||||
insta::assert_snapshot!("es_target", snapshot);
|
||||
});
|
||||
}
|
||||
|
|
@ -0,0 +1,54 @@
|
|||
---
|
||||
source: crates/oxc_transformer/tests/es_target/mod.rs
|
||||
snapshot_kind: text
|
||||
---
|
||||
########## 0 es5
|
||||
() => {}
|
||||
----------
|
||||
(function() {});
|
||||
|
||||
########## 1 es2015
|
||||
a ** b
|
||||
----------
|
||||
Math.pow(a, b);
|
||||
|
||||
########## 2 es2016
|
||||
async function foo() {}
|
||||
----------
|
||||
import _asyncToGenerator from '@babel/runtime/helpers/asyncToGenerator';
|
||||
function foo() {
|
||||
return _foo.apply(this, arguments);
|
||||
}
|
||||
function _foo() {
|
||||
_foo = _asyncToGenerator(function* () {});
|
||||
return _foo.apply(this, arguments);
|
||||
}
|
||||
|
||||
########## 3 es2017
|
||||
({ ...x })
|
||||
----------
|
||||
import _objectSpread from '@babel/runtime/helpers/objectSpread2';
|
||||
_objectSpread({}, x);
|
||||
|
||||
########## 4 es2018
|
||||
try {} catch {}
|
||||
----------
|
||||
try {} catch (_unused) {}
|
||||
|
||||
########## 5 es2019
|
||||
a ?? b
|
||||
----------
|
||||
var _a;
|
||||
(_a = a) !== null && _a !== void 0 ? _a : b;
|
||||
|
||||
########## 6 es2020
|
||||
a ||= b
|
||||
----------
|
||||
a || (a = b);
|
||||
|
||||
########## 7 es2021
|
||||
class foo { static {} }
|
||||
----------
|
||||
class foo {
|
||||
static #_ = (() => {})();
|
||||
}
|
||||
|
|
@ -1 +1,16 @@
|
|||
mod es_target;
|
||||
mod plugins;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{CodeGenerator, CodegenOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::SourceType;
|
||||
|
||||
pub fn run(source_text: &str, source_type: SourceType) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
CodeGenerator::new()
|
||||
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
||||
.build(&ret.program)
|
||||
.code
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ use oxc_semantic::SemanticBuilder;
|
|||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{InjectGlobalVariables, InjectGlobalVariablesConfig, InjectImport};
|
||||
|
||||
use super::run;
|
||||
use crate::run;
|
||||
|
||||
pub(crate) fn test(source_text: &str, expected: &str, config: InjectGlobalVariablesConfig) {
|
||||
let source_type = SourceType::default();
|
||||
|
|
|
|||
|
|
@ -1,16 +1,2 @@
|
|||
mod inject_global_variables;
|
||||
mod replace_global_defines;
|
||||
|
||||
use oxc_allocator::Allocator;
|
||||
use oxc_codegen::{CodeGenerator, CodegenOptions};
|
||||
use oxc_parser::Parser;
|
||||
use oxc_span::SourceType;
|
||||
|
||||
fn run(source_text: &str, source_type: SourceType) -> String {
|
||||
let allocator = Allocator::default();
|
||||
let ret = Parser::new(&allocator, source_text, source_type).parse();
|
||||
CodeGenerator::new()
|
||||
.with_options(CodegenOptions { single_quote: true, ..CodegenOptions::default() })
|
||||
.build(&ret.program)
|
||||
.code
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ use oxc_semantic::SemanticBuilder;
|
|||
use oxc_span::SourceType;
|
||||
use oxc_transformer::{ReplaceGlobalDefines, ReplaceGlobalDefinesConfig};
|
||||
|
||||
use super::run;
|
||||
use crate::run;
|
||||
|
||||
pub(crate) fn test(source_text: &str, expected: &str, config: ReplaceGlobalDefinesConfig) {
|
||||
let source_type = SourceType::default();
|
||||
|
|
|
|||
Loading…
Reference in a new issue