oxc_transformer/options/
mod.rs

1use std::path::PathBuf;
2
3use crate::{
4    ReactRefreshOptions,
5    common::helper_loader::{HelperLoaderMode, HelperLoaderOptions},
6    compiler_assumptions::CompilerAssumptions,
7    decorator::DecoratorOptions,
8    es2015::ES2015Options,
9    es2016::ES2016Options,
10    es2017::ES2017Options,
11    es2018::ES2018Options,
12    es2019::ES2019Options,
13    es2020::ES2020Options,
14    es2021::ES2021Options,
15    es2022::ES2022Options,
16    es2026::ES2026Options,
17    jsx::JsxOptions,
18    plugins::{PluginsOptions, StyledComponentsOptions},
19    proposals::ProposalOptions,
20    regexp::RegExpOptions,
21    typescript::TypeScriptOptions,
22};
23
24pub mod babel;
25mod env;
26mod module;
27
28use babel::BabelOptions;
29pub use env::EnvOptions;
30pub use module::Module;
31pub use oxc_compat::{Engine, EngineTargets};
32pub use oxc_syntax::es_target::ESTarget;
33
34/// <https://babel.dev/docs/options>
35#[derive(Debug, Default, Clone)]
36pub struct TransformOptions {
37    //
38    // Primary Options
39    //
40    /// The working directory that all paths in the programmatic options will be resolved relative to.
41    pub cwd: PathBuf,
42
43    // Core
44    /// Set assumptions in order to produce smaller output.
45    /// For more information, check the [assumptions](https://babel.dev/docs/assumptions) documentation page.
46    pub assumptions: CompilerAssumptions,
47
48    // Plugins
49    /// [preset-typescript](https://babeljs.io/docs/babel-preset-typescript)
50    pub typescript: TypeScriptOptions,
51
52    /// Decorator
53    pub decorator: DecoratorOptions,
54
55    /// Jsx Transform
56    ///
57    /// See [preset-react](https://babeljs.io/docs/babel-preset-react)
58    pub jsx: JsxOptions,
59
60    /// ECMAScript Env Options
61    pub env: EnvOptions,
62
63    /// Proposals
64    pub proposals: ProposalOptions,
65
66    /// Plugins
67    pub plugins: PluginsOptions,
68
69    pub helper_loader: HelperLoaderOptions,
70}
71
72impl TransformOptions {
73    /// Explicitly enable all plugins that are ready, mainly for testing purposes.
74    ///
75    /// NOTE: for internal use only
76    #[doc(hidden)]
77    pub fn enable_all() -> Self {
78        Self {
79            cwd: PathBuf::new(),
80            assumptions: CompilerAssumptions::default(),
81            typescript: TypeScriptOptions::default(),
82            decorator: DecoratorOptions { legacy: true, emit_decorator_metadata: true },
83            jsx: JsxOptions {
84                development: true,
85                refresh: Some(ReactRefreshOptions::default()),
86                ..JsxOptions::default()
87            },
88            env: EnvOptions::enable_all(/* include_unfinished_plugins */ false),
89            proposals: ProposalOptions::default(),
90            plugins: PluginsOptions {
91                styled_components: Some(StyledComponentsOptions::default()),
92                tagged_template_transform: true,
93            },
94            helper_loader: HelperLoaderOptions {
95                mode: HelperLoaderMode::Runtime,
96                ..Default::default()
97            },
98        }
99    }
100
101    /// Initialize from a comma separated list of `target`s and `environmens`s.
102    ///
103    /// e.g. `es2022,chrome58,edge16`.
104    ///
105    /// # Errors
106    ///
107    /// * Same targets specified multiple times.
108    /// * No matching target.
109    /// * Invalid version.
110    pub fn from_target(s: &str) -> Result<Self, String> {
111        EnvOptions::from_target(s).map(|env| Self { env, ..Self::default() })
112    }
113
114    /// Initialize from a list of `target`s and `environmens`s.
115    ///
116    /// e.g. `["es2020", "chrome58", "edge16", "firefox57", "node12", "safari11"]`.
117    ///
118    /// `target`: `es5`, `es2015` ... `es2024`, `esnext`.
119    /// `environment`: `chrome`, `deno`, `edge`, `firefox`, `hermes`, `ie`, `ios`, `node`, `opera`, `rhino`, `safari`
120    ///
121    /// <https://esbuild.github.io/api/#target>
122    ///
123    /// # Errors
124    ///
125    /// * Same targets specified multiple times.
126    /// * No matching target.
127    /// * Invalid version.
128    pub fn from_target_list<S: AsRef<str>>(list: &[S]) -> Result<Self, String> {
129        EnvOptions::from_target_list(list).map(|env| Self { env, ..Self::default() })
130    }
131}
132
133impl From<ESTarget> for TransformOptions {
134    fn from(target: ESTarget) -> Self {
135        use oxc_compat::ESVersion;
136        let mut engine_targets = EngineTargets::default();
137        engine_targets.insert(Engine::Es, target.version());
138        let env = EnvOptions::from(engine_targets);
139        Self { env, ..Self::default() }
140    }
141}
142
143impl TryFrom<&BabelOptions> for TransformOptions {
144    type Error = Vec<String>;
145
146    /// If the `options` contains any unknown fields, they will be returned as a list of errors.
147    fn try_from(options: &BabelOptions) -> Result<Self, Self::Error> {
148        let mut errors = Vec::<String>::new();
149        errors.extend(options.plugins.errors.iter().map(Clone::clone));
150        errors.extend(options.presets.errors.iter().map(Clone::clone));
151
152        let typescript = options
153            .presets
154            .typescript
155            .clone()
156            .or_else(|| options.plugins.typescript.clone())
157            .unwrap_or_default();
158
159        let decorator = DecoratorOptions {
160            legacy: options.plugins.legacy_decorator.is_some(),
161            emit_decorator_metadata: options
162                .plugins
163                .legacy_decorator
164                .is_some_and(|o| o.emit_decorator_metadata),
165        };
166
167        let jsx = if let Some(options) = &options.presets.jsx {
168            options.clone()
169        } else {
170            let mut jsx_options = if let Some(options) = &options.plugins.react_jsx_dev {
171                options.clone()
172            } else if let Some(options) = &options.plugins.react_jsx {
173                options.clone()
174            } else {
175                JsxOptions::default()
176            };
177            jsx_options.development = options.plugins.react_jsx_dev.is_some();
178            jsx_options.jsx_plugin = options.plugins.react_jsx.is_some();
179            jsx_options.display_name_plugin = options.plugins.react_display_name;
180            jsx_options.jsx_self_plugin = options.plugins.react_jsx_self;
181            jsx_options.jsx_source_plugin = options.plugins.react_jsx_source;
182            jsx_options
183        };
184
185        let env = options.presets.env.unwrap_or_default();
186
187        let module = Module::try_from(&options.plugins).unwrap_or_else(|_| {
188            options.presets.env.as_ref().map(|env| env.module).unwrap_or_default()
189        });
190
191        let regexp = RegExpOptions {
192            sticky_flag: env.regexp.sticky_flag || options.plugins.sticky_flag,
193            unicode_flag: env.regexp.unicode_flag || options.plugins.unicode_flag,
194            dot_all_flag: env.regexp.dot_all_flag || options.plugins.dot_all_flag,
195            look_behind_assertions: env.regexp.look_behind_assertions
196                || options.plugins.look_behind_assertions,
197            named_capture_groups: env.regexp.named_capture_groups
198                || options.plugins.named_capture_groups,
199            unicode_property_escapes: env.regexp.unicode_property_escapes
200                || options.plugins.unicode_property_escapes,
201            match_indices: env.regexp.match_indices,
202            set_notation: env.regexp.set_notation || options.plugins.set_notation,
203        };
204
205        let es2015 = ES2015Options {
206            arrow_function: options.plugins.arrow_function.or(env.es2015.arrow_function),
207        };
208
209        let es2016 = ES2016Options {
210            exponentiation_operator: options.plugins.exponentiation_operator
211                || env.es2016.exponentiation_operator,
212        };
213
214        let es2017 = ES2017Options {
215            async_to_generator: options.plugins.async_to_generator || env.es2017.async_to_generator,
216        };
217
218        let es2018 = ES2018Options {
219            object_rest_spread: options
220                .plugins
221                .object_rest_spread
222                .or(env.es2018.object_rest_spread),
223            async_generator_functions: options.plugins.async_generator_functions
224                || env.es2018.async_generator_functions,
225        };
226
227        let es2019 = ES2019Options {
228            optional_catch_binding: options.plugins.optional_catch_binding
229                || env.es2019.optional_catch_binding,
230        };
231
232        let es2020 = ES2020Options {
233            export_namespace_from: options.plugins.export_namespace_from
234                || env.es2020.export_namespace_from,
235            optional_chaining: options.plugins.optional_chaining || env.es2020.optional_chaining,
236            nullish_coalescing_operator: options.plugins.nullish_coalescing_operator
237                || env.es2020.nullish_coalescing_operator,
238            big_int: env.es2020.big_int,
239            arbitrary_module_namespace_names: env.es2020.arbitrary_module_namespace_names,
240        };
241
242        let es2021 = ES2021Options {
243            logical_assignment_operators: options.plugins.logical_assignment_operators
244                || env.es2021.logical_assignment_operators,
245        };
246
247        let es2022 = ES2022Options {
248            class_static_block: options.plugins.class_static_block || env.es2022.class_static_block,
249            class_properties: options.plugins.class_properties.or(env.es2022.class_properties),
250            top_level_await: env.es2022.top_level_await,
251        };
252
253        if !errors.is_empty() {
254            return Err(errors);
255        }
256
257        let helper_loader = HelperLoaderOptions {
258            mode: if options.external_helpers {
259                HelperLoaderMode::External
260            } else {
261                HelperLoaderMode::default()
262            },
263            ..HelperLoaderOptions::default()
264        };
265
266        let mut plugins = PluginsOptions::default();
267        if let Some(styled_components) = &options.plugins.styled_components {
268            plugins.styled_components = Some(styled_components.clone());
269        }
270        plugins.tagged_template_transform = options.plugins.tagged_template_escape;
271
272        Ok(Self {
273            cwd: options.cwd.clone().unwrap_or_default(),
274            assumptions: options.assumptions,
275            typescript,
276            decorator,
277            jsx,
278            env: EnvOptions {
279                module,
280                regexp,
281                es2015,
282                es2016,
283                es2017,
284                es2018,
285                es2019,
286                es2020,
287                es2021,
288                es2022,
289                es2026: ES2026Options {
290                    explicit_resource_management: options.plugins.explicit_resource_management,
291                },
292            },
293            proposals: ProposalOptions::default(),
294            helper_loader,
295            plugins,
296        })
297    }
298}