feat(transformer)!: change API to take a &TransformOptions instead of TransformOptions (#7213)

closes #7185

`TransformOption`s has an initialization cost, it should be initialized once and shared across files.
This commit is contained in:
Boshen 2024-11-09 06:01:13 +00:00
parent 092de67c47
commit 846711cf41
24 changed files with 103 additions and 80 deletions

View file

@ -65,8 +65,8 @@ pub trait CompilerInterface {
None None
} }
fn transform_options(&self) -> Option<TransformOptions> { fn transform_options(&self) -> Option<&TransformOptions> {
Some(TransformOptions::default()) None
} }
fn define_options(&self) -> Option<ReplaceGlobalDefinesConfig> { fn define_options(&self) -> Option<ReplaceGlobalDefinesConfig> {
@ -267,7 +267,7 @@ pub trait CompilerInterface {
#[allow(clippy::too_many_arguments)] #[allow(clippy::too_many_arguments)]
fn transform<'a>( fn transform<'a>(
&self, &self,
options: TransformOptions, options: &TransformOptions,
allocator: &'a Allocator, allocator: &'a Allocator,
program: &mut Program<'a>, program: &mut Program<'a>,
source_path: &Path, source_path: &Path,

View file

@ -66,7 +66,7 @@ fn main() {
TransformOptions::enable_all() TransformOptions::enable_all()
}; };
let ret = Transformer::new(&allocator, path, transform_options).build_with_symbols_and_scopes( let ret = Transformer::new(&allocator, path, &transform_options).build_with_symbols_and_scopes(
symbols, symbols,
scopes, scopes,
&mut program, &mut program,

View file

@ -98,7 +98,7 @@ use oxc_syntax::{
use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx}; use oxc_traverse::{Ancestor, BoundIdentifier, Traverse, TraverseCtx};
use rustc_hash::FxHashMap; use rustc_hash::FxHashMap;
use crate::TransformOptions; use crate::EnvOptions;
/// Mode for arrow function conversion /// Mode for arrow function conversion
#[derive(Debug, Clone, Copy, PartialEq, Eq)] #[derive(Debug, Clone, Copy, PartialEq, Eq)]
@ -129,12 +129,10 @@ pub struct ArrowFunctionConverter<'a> {
} }
impl<'a> ArrowFunctionConverter<'a> { impl<'a> ArrowFunctionConverter<'a> {
pub fn new(options: &TransformOptions) -> Self { pub fn new(env: &EnvOptions) -> Self {
let mode = if options.env.es2015.arrow_function.is_some() { let mode = if env.es2015.arrow_function.is_some() {
ArrowFunctionConverterMode::Enabled ArrowFunctionConverterMode::Enabled
} else if options.env.es2017.async_to_generator } else if env.es2017.async_to_generator || env.es2018.async_generator_functions {
|| options.env.es2018.async_generator_functions
{
ArrowFunctionConverterMode::AsyncOnly ArrowFunctionConverterMode::AsyncOnly
} else { } else {
ArrowFunctionConverterMode::Disabled ArrowFunctionConverterMode::Disabled

View file

@ -5,7 +5,7 @@ use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*; use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx}; use oxc_traverse::{Traverse, TraverseCtx};
use crate::{TransformCtx, TransformOptions}; use crate::{EnvOptions, TransformCtx};
pub mod arrow_function_converter; pub mod arrow_function_converter;
pub mod helper_loader; pub mod helper_loader;
@ -28,7 +28,7 @@ pub struct Common<'a, 'ctx> {
} }
impl<'a, 'ctx> Common<'a, 'ctx> { impl<'a, 'ctx> Common<'a, 'ctx> {
pub fn new(options: &TransformOptions, ctx: &'ctx TransformCtx<'a>) -> Self { pub fn new(options: &EnvOptions, ctx: &'ctx TransformCtx<'a>) -> Self {
Self { Self {
module_imports: ModuleImports::new(ctx), module_imports: ModuleImports::new(ctx),
var_declarations: VarDeclarations::new(ctx), var_declarations: VarDeclarations::new(ctx),

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::ArrowFunctionsOptions; use super::ArrowFunctionsOptions;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2015Options { pub struct ES2015Options {
#[serde(skip)] #[serde(skip)]

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2016Options { pub struct ES2016Options {
#[serde(skip)] #[serde(skip)]

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2017Options { pub struct ES2017Options {
#[serde(skip)] #[serde(skip)]

View file

@ -2,7 +2,7 @@ use serde::Deserialize;
use super::ObjectRestSpreadOptions; use super::ObjectRestSpreadOptions;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2018Options { pub struct ES2018Options {
#[serde(skip)] #[serde(skip)]

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2019Options { pub struct ES2019Options {
#[serde(skip)] #[serde(skip)]

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2020Options { pub struct ES2020Options {
#[serde(skip)] #[serde(skip)]

View file

@ -1,6 +1,6 @@
use serde::Deserialize; use serde::Deserialize;
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(default, rename_all = "camelCase", deny_unknown_fields)] #[serde(default, rename_all = "camelCase", deny_unknown_fields)]
pub struct ES2021Options { pub struct ES2021Options {
#[serde(skip)] #[serde(skip)]

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use oxc_ast::{Comment, CommentKind}; use oxc_ast::{Comment, CommentKind};
use oxc_syntax::identifier::is_irregular_whitespace; use oxc_syntax::identifier::is_irregular_whitespace;
use crate::{JsxRuntime, TransformCtx, TransformOptions}; use crate::{JsxOptions, JsxRuntime, TransformCtx, TypeScriptOptions};
/// Scan through all comments and find the following pragmas: /// Scan through all comments and find the following pragmas:
/// ///
@ -18,16 +18,18 @@ use crate::{JsxRuntime, TransformCtx, TransformOptions};
/// This behavior is aligned with Babel. /// This behavior is aligned with Babel.
pub(crate) fn update_options_with_comments( pub(crate) fn update_options_with_comments(
comments: &[Comment], comments: &[Comment],
options: &mut TransformOptions, typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
ctx: &TransformCtx, ctx: &TransformCtx,
) { ) {
for comment in comments { for comment in comments {
update_options_with_comment(options, comment, ctx.source_text); update_options_with_comment(typescript, jsx, comment, ctx.source_text);
} }
} }
fn update_options_with_comment( fn update_options_with_comment(
options: &mut TransformOptions, typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
comment: &Comment, comment: &Comment,
source_text: &str, source_text: &str,
) { ) {
@ -38,14 +40,14 @@ fn update_options_with_comment(
"" => { "" => {
// Don't set React option unless React transform is enabled // Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new` // otherwise can cause error in `ReactJsx::new`
if options.jsx.jsx_plugin || options.jsx.development { if jsx.jsx_plugin || jsx.development {
options.jsx.pragma = Some(remainder.to_string()); jsx.pragma = Some(remainder.to_string());
} }
options.typescript.jsx_pragma = Cow::from(remainder.to_string()); typescript.jsx_pragma = Cow::from(remainder.to_string());
} }
// @jsxRuntime // @jsxRuntime
"Runtime" => { "Runtime" => {
options.jsx.runtime = match remainder { jsx.runtime = match remainder {
"classic" => JsxRuntime::Classic, "classic" => JsxRuntime::Classic,
"automatic" => JsxRuntime::Automatic, "automatic" => JsxRuntime::Automatic,
_ => return, _ => return,
@ -53,16 +55,16 @@ fn update_options_with_comment(
} }
// @jsxImportSource // @jsxImportSource
"ImportSource" => { "ImportSource" => {
options.jsx.import_source = Some(remainder.to_string()); jsx.import_source = Some(remainder.to_string());
} }
// @jsxFrag // @jsxFrag
"Frag" => { "Frag" => {
// Don't set React option unless React transform is enabled // Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new` // otherwise can cause error in `ReactJsx::new`
if options.jsx.jsx_plugin || options.jsx.development { if jsx.jsx_plugin || jsx.development {
options.jsx.pragma_frag = Some(remainder.to_string()); jsx.pragma_frag = Some(remainder.to_string());
} }
options.typescript.jsx_pragma_frag = Cow::from(remainder.to_string()); typescript.jsx_pragma_frag = Cow::from(remainder.to_string());
} }
_ => {} _ => {}
} }

View file

@ -70,14 +70,24 @@ pub struct TransformerReturn {
pub struct Transformer<'a> { pub struct Transformer<'a> {
ctx: TransformCtx<'a>, ctx: TransformCtx<'a>,
options: TransformOptions, // options: TransformOptions,
allocator: &'a Allocator, allocator: &'a Allocator,
typescript: TypeScriptOptions,
jsx: JsxOptions,
env: EnvOptions,
} }
impl<'a> Transformer<'a> { impl<'a> Transformer<'a> {
pub fn new(allocator: &'a Allocator, source_path: &Path, options: TransformOptions) -> Self { pub fn new(allocator: &'a Allocator, source_path: &Path, options: &TransformOptions) -> Self {
let ctx = TransformCtx::new(source_path, &options); let ctx = TransformCtx::new(source_path, options);
Self { ctx, options, allocator } Self {
ctx,
allocator,
typescript: options.typescript.clone(),
jsx: options.jsx.clone(),
env: options.env,
}
} }
pub fn build_with_symbols_and_scopes( pub fn build_with_symbols_and_scopes(
@ -91,24 +101,29 @@ impl<'a> Transformer<'a> {
self.ctx.source_type = program.source_type; self.ctx.source_type = program.source_type;
self.ctx.source_text = program.source_text; self.ctx.source_text = program.source_text;
jsx::update_options_with_comments(&program.comments, &mut self.options, &self.ctx); jsx::update_options_with_comments(
&program.comments,
&mut self.typescript,
&mut self.jsx,
&self.ctx,
);
let mut transformer = TransformerImpl { let mut transformer = TransformerImpl {
common: Common::new(&self.options, &self.ctx), common: Common::new(&self.env, &self.ctx),
x0_typescript: program x0_typescript: program
.source_type .source_type
.is_typescript() .is_typescript()
.then(|| TypeScript::new(&self.options.typescript, &self.ctx)), .then(|| TypeScript::new(&self.typescript, &self.ctx)),
x1_jsx: Jsx::new(self.options.jsx, ast_builder, &self.ctx), x1_jsx: Jsx::new(self.jsx, ast_builder, &self.ctx),
x2_es2022: ES2022::new(self.options.env.es2022, &self.ctx), x2_es2022: ES2022::new(self.env.es2022, &self.ctx),
x2_es2021: ES2021::new(self.options.env.es2021, &self.ctx), x2_es2021: ES2021::new(self.env.es2021, &self.ctx),
x2_es2020: ES2020::new(self.options.env.es2020, &self.ctx), x2_es2020: ES2020::new(self.env.es2020, &self.ctx),
x2_es2019: ES2019::new(self.options.env.es2019), x2_es2019: ES2019::new(self.env.es2019),
x2_es2018: ES2018::new(self.options.env.es2018, &self.ctx), x2_es2018: ES2018::new(self.env.es2018, &self.ctx),
x2_es2016: ES2016::new(self.options.env.es2016, &self.ctx), x2_es2016: ES2016::new(self.env.es2016, &self.ctx),
x2_es2017: ES2017::new(self.options.env.es2017, &self.ctx), x2_es2017: ES2017::new(self.env.es2017, &self.ctx),
x3_es2015: ES2015::new(self.options.env.es2015, &self.ctx), x3_es2015: ES2015::new(self.env.es2015, &self.ctx),
x4_regexp: RegExp::new(self.options.env.regexp, &self.ctx), x4_regexp: RegExp::new(self.env.regexp, &self.ctx),
}; };
let (symbols, scopes) = traverse_mut(&mut transformer, allocator, program, symbols, scopes); let (symbols, scopes) = traverse_mut(&mut transformer, allocator, program, symbols, scopes);

View file

@ -18,7 +18,7 @@ use crate::{
use super::{babel::BabelEnvOptions, ESFeature, ESTarget, Engine}; use super::{babel::BabelEnvOptions, ESFeature, ESTarget, Engine};
#[derive(Debug, Default, Clone, Deserialize)] #[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(try_from = "BabelEnvOptions")] #[serde(try_from = "BabelEnvOptions")]
pub struct EnvOptions { pub struct EnvOptions {
pub regexp: RegExpOptions, pub regexp: RegExpOptions,

View file

@ -161,7 +161,7 @@ impl TryFrom<&BabelOptions> for TransformOptions {
jsx_options jsx_options
}; };
let env = options.presets.env.clone().unwrap_or_default(); let env = options.presets.env.unwrap_or_default();
let regexp = RegExpOptions { let regexp = RegExpOptions {
sticky_flag: env.regexp.sticky_flag || options.plugins.sticky_flag, sticky_flag: env.regexp.sticky_flag || options.plugins.sticky_flag,

View file

@ -21,15 +21,15 @@ fn es_target() {
]; ];
// Test no transformation for esnext. // Test no transformation for esnext.
for (_, case) in cases {
let options = TransformOptions::from(ESTarget::from_str("esnext").unwrap()); let options = TransformOptions::from(ESTarget::from_str("esnext").unwrap());
assert_eq!(test(case, options), Ok(codegen(case, SourceType::mjs()))); for (_, case) in cases {
assert_eq!(test(case, &options), Ok(codegen(case, SourceType::mjs())));
} }
let snapshot = let snapshot =
cases.into_iter().enumerate().fold(String::new(), |mut w, (i, (target, case))| { cases.into_iter().enumerate().fold(String::new(), |mut w, (i, (target, case))| {
let options = TransformOptions::from_target(target).unwrap(); let options = TransformOptions::from_target(target).unwrap();
let result = match test(case, options) { let result = match test(case, &options) {
Ok(code) => code, Ok(code) => code,
Err(errors) => errors Err(errors) => errors
.into_iter() .into_iter()

View file

@ -23,7 +23,7 @@ pub fn codegen(source_text: &str, source_type: SourceType) -> String {
pub(crate) fn test( pub(crate) fn test(
source_text: &str, source_text: &str,
options: TransformOptions, options: &TransformOptions,
) -> Result<String, Vec<OxcDiagnostic>> { ) -> Result<String, Vec<OxcDiagnostic>> {
let source_type = SourceType::default(); let source_type = SourceType::default();
let allocator = Allocator::default(); let allocator = Allocator::default();

View file

@ -16,21 +16,21 @@ fn targets() {
]; ];
// Test no transformation for default targets. // Test no transformation for default targets.
for case in cases {
let options = TransformOptions { let options = TransformOptions {
env: EnvOptions::from_browserslist_query("defaults").unwrap(), env: EnvOptions::from_browserslist_query("defaults").unwrap(),
..TransformOptions::default() ..TransformOptions::default()
}; };
assert_eq!(Ok(codegen(case, SourceType::mjs())), test(case, options)); for case in cases {
assert_eq!(Ok(codegen(case, SourceType::mjs())), test(case, &options));
} }
// Test transformation for very low targets. // Test transformation for very low targets.
for case in cases {
let options = TransformOptions::from(ESTarget::ES5); let options = TransformOptions::from(ESTarget::ES5);
let options_node = TransformOptions { let options_node = TransformOptions {
env: EnvOptions::from_browserslist_query("node 0.10").unwrap(), env: EnvOptions::from_browserslist_query("node 0.10").unwrap(),
..TransformOptions::default() ..TransformOptions::default()
}; };
assert_eq!(test(case, options), test(case, options_node)); for case in cases {
assert_eq!(test(case, &options), test(case, &options_node));
} }
} }

View file

@ -243,7 +243,7 @@ impl Oxc {
if run_options.transform.unwrap_or_default() { if run_options.transform.unwrap_or_default() {
let options = TransformOptions::enable_all(); let options = TransformOptions::enable_all();
let result = Transformer::new(&allocator, &path, options) let result = Transformer::new(&allocator, &path, &options)
.build_with_symbols_and_scopes(symbols, scopes, &mut program); .build_with_symbols_and_scopes(symbols, scopes, &mut program);
if !result.errors.is_empty() { if !result.errors.is_empty() {
self.save_diagnostics( self.save_diagnostics(

View file

@ -111,8 +111,8 @@ impl CompilerInterface for Compiler {
self.sourcemap self.sourcemap
} }
fn transform_options(&self) -> Option<oxc::transformer::TransformOptions> { fn transform_options(&self) -> Option<&oxc::transformer::TransformOptions> {
Some(self.transform_options.clone()) Some(&self.transform_options)
} }
fn isolated_declaration_options(&self) -> Option<IsolatedDeclarationsOptions> { fn isolated_declaration_options(&self) -> Option<IsolatedDeclarationsOptions> {

View file

@ -20,6 +20,11 @@ fn bench_transformer(criterion: &mut Criterion) {
// both the warmup and measurement phases // both the warmup and measurement phases
let mut allocator = Allocator::default(); let mut allocator = Allocator::default();
let mut transform_options = TransformOptions::enable_all();
// Even the plugins are unfinished, we still want to enable all of them
// to track the performance changes during the development.
transform_options.env = EnvOptions::enable_all(/* include_unfinished_plugins */ true);
group.bench_function(id, |b| { group.bench_function(id, |b| {
b.iter_with_setup_wrapper(|runner| { b.iter_with_setup_wrapper(|runner| {
// Reset allocator at start of each iteration // Reset allocator at start of each iteration
@ -35,14 +40,17 @@ fn bench_transformer(criterion: &mut Criterion) {
.semantic .semantic
.into_symbol_table_and_scope_tree(); .into_symbol_table_and_scope_tree();
let mut options = TransformOptions::enable_all();
// Even the plugins are unfinished, we still want to enable all of them
// to track the performance changes during the development.
options.env = EnvOptions::enable_all(/* include_unfinished_plugins */ true);
runner.run(|| { runner.run(|| {
let ret = Transformer::new(&allocator, Path::new(&file.file_name), options) let ret = Transformer::new(
.build_with_symbols_and_scopes(symbols, scopes, &mut program); &allocator,
Path::new(&file.file_name),
&transform_options,
)
.build_with_symbols_and_scopes(
symbols,
scopes,
&mut program,
);
// Return the `TransformerReturn`, so it's dropped outside of the measured section. // Return the `TransformerReturn`, so it's dropped outside of the measured section.
// `TransformerReturn` contains `ScopeTree` and `SymbolTable` which are costly to drop. // `TransformerReturn` contains `ScopeTree` and `SymbolTable` which are costly to drop.

View file

@ -49,8 +49,8 @@ impl CompilerInterface for Driver {
true true
} }
fn transform_options(&self) -> Option<TransformOptions> { fn transform_options(&self) -> Option<&TransformOptions> {
self.transform.clone() self.transform.as_ref()
} }
fn compress_options(&self) -> Option<CompressOptions> { fn compress_options(&self) -> Option<CompressOptions> {

View file

@ -173,7 +173,7 @@ impl Test262RuntimeCase {
options.jsx.refresh = None; options.jsx.refresh = None;
options.helper_loader.mode = HelperLoaderMode::External; options.helper_loader.mode = HelperLoaderMode::External;
options.typescript.only_remove_type_imports = true; options.typescript.only_remove_type_imports = true;
Transformer::new(&allocator, self.path(), options).build_with_symbols_and_scopes( Transformer::new(&allocator, self.path(), &options).build_with_symbols_and_scopes(
symbols, symbols,
scopes, scopes,
&mut program, &mut program,

View file

@ -18,8 +18,8 @@ pub struct Driver {
} }
impl CompilerInterface for Driver { impl CompilerInterface for Driver {
fn transform_options(&self) -> Option<TransformOptions> { fn transform_options(&self) -> Option<&TransformOptions> {
Some(self.options.clone()) Some(&self.options)
} }
fn codegen_options(&self) -> Option<CodegenOptions> { fn codegen_options(&self) -> Option<CodegenOptions> {