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

View file

@ -66,7 +66,7 @@ fn main() {
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,
scopes,
&mut program,

View file

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

View file

@ -5,7 +5,7 @@ use oxc_allocator::Vec as ArenaVec;
use oxc_ast::ast::*;
use oxc_traverse::{Traverse, TraverseCtx};
use crate::{TransformCtx, TransformOptions};
use crate::{EnvOptions, TransformCtx};
pub mod arrow_function_converter;
pub mod helper_loader;
@ -28,7 +28,7 @@ pub struct 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 {
module_imports: ModuleImports::new(ctx),
var_declarations: VarDeclarations::new(ctx),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

View file

@ -3,7 +3,7 @@ use std::borrow::Cow;
use oxc_ast::{Comment, CommentKind};
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:
///
@ -18,16 +18,18 @@ use crate::{JsxRuntime, TransformCtx, TransformOptions};
/// This behavior is aligned with Babel.
pub(crate) fn update_options_with_comments(
comments: &[Comment],
options: &mut TransformOptions,
typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
ctx: &TransformCtx,
) {
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(
options: &mut TransformOptions,
typescript: &mut TypeScriptOptions,
jsx: &mut JsxOptions,
comment: &Comment,
source_text: &str,
) {
@ -38,14 +40,14 @@ fn update_options_with_comment(
"" => {
// Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new`
if options.jsx.jsx_plugin || options.jsx.development {
options.jsx.pragma = Some(remainder.to_string());
if jsx.jsx_plugin || jsx.development {
jsx.pragma = Some(remainder.to_string());
}
options.typescript.jsx_pragma = Cow::from(remainder.to_string());
typescript.jsx_pragma = Cow::from(remainder.to_string());
}
// @jsxRuntime
"Runtime" => {
options.jsx.runtime = match remainder {
jsx.runtime = match remainder {
"classic" => JsxRuntime::Classic,
"automatic" => JsxRuntime::Automatic,
_ => return,
@ -53,16 +55,16 @@ fn update_options_with_comment(
}
// @jsxImportSource
"ImportSource" => {
options.jsx.import_source = Some(remainder.to_string());
jsx.import_source = Some(remainder.to_string());
}
// @jsxFrag
"Frag" => {
// Don't set React option unless React transform is enabled
// otherwise can cause error in `ReactJsx::new`
if options.jsx.jsx_plugin || options.jsx.development {
options.jsx.pragma_frag = Some(remainder.to_string());
if jsx.jsx_plugin || jsx.development {
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> {
ctx: TransformCtx<'a>,
options: TransformOptions,
// options: TransformOptions,
allocator: &'a Allocator,
typescript: TypeScriptOptions,
jsx: JsxOptions,
env: EnvOptions,
}
impl<'a> Transformer<'a> {
pub fn new(allocator: &'a Allocator, source_path: &Path, options: TransformOptions) -> Self {
let ctx = TransformCtx::new(source_path, &options);
Self { ctx, options, allocator }
pub fn new(allocator: &'a Allocator, source_path: &Path, options: &TransformOptions) -> Self {
let ctx = TransformCtx::new(source_path, options);
Self {
ctx,
allocator,
typescript: options.typescript.clone(),
jsx: options.jsx.clone(),
env: options.env,
}
}
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_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 {
common: Common::new(&self.options, &self.ctx),
common: Common::new(&self.env, &self.ctx),
x0_typescript: program
.source_type
.is_typescript()
.then(|| TypeScript::new(&self.options.typescript, &self.ctx)),
x1_jsx: Jsx::new(self.options.jsx, ast_builder, &self.ctx),
x2_es2022: ES2022::new(self.options.env.es2022, &self.ctx),
x2_es2021: ES2021::new(self.options.env.es2021, &self.ctx),
x2_es2020: ES2020::new(self.options.env.es2020, &self.ctx),
x2_es2019: ES2019::new(self.options.env.es2019),
x2_es2018: ES2018::new(self.options.env.es2018, &self.ctx),
x2_es2016: ES2016::new(self.options.env.es2016, &self.ctx),
x2_es2017: ES2017::new(self.options.env.es2017, &self.ctx),
x3_es2015: ES2015::new(self.options.env.es2015, &self.ctx),
x4_regexp: RegExp::new(self.options.env.regexp, &self.ctx),
.then(|| TypeScript::new(&self.typescript, &self.ctx)),
x1_jsx: Jsx::new(self.jsx, ast_builder, &self.ctx),
x2_es2022: ES2022::new(self.env.es2022, &self.ctx),
x2_es2021: ES2021::new(self.env.es2021, &self.ctx),
x2_es2020: ES2020::new(self.env.es2020, &self.ctx),
x2_es2019: ES2019::new(self.env.es2019),
x2_es2018: ES2018::new(self.env.es2018, &self.ctx),
x2_es2016: ES2016::new(self.env.es2016, &self.ctx),
x2_es2017: ES2017::new(self.env.es2017, &self.ctx),
x3_es2015: ES2015::new(self.env.es2015, &self.ctx),
x4_regexp: RegExp::new(self.env.regexp, &self.ctx),
};
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};
#[derive(Debug, Default, Clone, Deserialize)]
#[derive(Debug, Default, Clone, Copy, Deserialize)]
#[serde(try_from = "BabelEnvOptions")]
pub struct EnvOptions {
pub regexp: RegExpOptions,

View file

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

View file

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

View file

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

View file

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

View file

@ -243,7 +243,7 @@ impl Oxc {
if run_options.transform.unwrap_or_default() {
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);
if !result.errors.is_empty() {
self.save_diagnostics(

View file

@ -111,8 +111,8 @@ impl CompilerInterface for Compiler {
self.sourcemap
}
fn transform_options(&self) -> Option<oxc::transformer::TransformOptions> {
Some(self.transform_options.clone())
fn transform_options(&self) -> Option<&oxc::transformer::TransformOptions> {
Some(&self.transform_options)
}
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
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| {
b.iter_with_setup_wrapper(|runner| {
// Reset allocator at start of each iteration
@ -35,14 +40,17 @@ fn bench_transformer(criterion: &mut Criterion) {
.semantic
.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(|| {
let ret = Transformer::new(&allocator, Path::new(&file.file_name), options)
.build_with_symbols_and_scopes(symbols, scopes, &mut program);
let ret = Transformer::new(
&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.
// `TransformerReturn` contains `ScopeTree` and `SymbolTable` which are costly to drop.

View file

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

View file

@ -173,7 +173,7 @@ impl Test262RuntimeCase {
options.jsx.refresh = None;
options.helper_loader.mode = HelperLoaderMode::External;
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,
scopes,
&mut program,

View file

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