oxc_transform_napi/
transformer.rs

1// NOTE: Types must be aligned with [@types/babel__core](https://github.com/DefinitelyTyped/DefinitelyTyped/blob/b5dc32740d9b45d11cff9b025896dd333c795b39/types/babel__core/index.d.ts).
2#![expect(rustdoc::bare_urls)]
3
4use std::{
5    ops::ControlFlow,
6    path::{Path, PathBuf},
7};
8
9use napi::{Either, Task, bindgen_prelude::AsyncTask};
10use napi_derive::napi;
11use rustc_hash::FxHashMap;
12
13use oxc::{
14    CompilerInterface,
15    allocator::Allocator,
16    codegen::{Codegen, CodegenOptions, CodegenReturn},
17    diagnostics::OxcDiagnostic,
18    parser::Parser,
19    semantic::{SemanticBuilder, SemanticBuilderReturn},
20    span::SourceType,
21    transformer::{
22        EnvOptions, HelperLoaderMode, HelperLoaderOptions, JsxRuntime, ProposalOptions,
23        RewriteExtensionsMode,
24    },
25    transformer_plugins::{
26        InjectGlobalVariablesConfig, InjectImport, ModuleRunnerTransform,
27        ReplaceGlobalDefinesConfig,
28    },
29};
30use oxc_napi::{OxcError, get_source_type};
31use oxc_sourcemap::napi::SourceMap;
32
33use crate::IsolatedDeclarationsOptions;
34
35#[derive(Default)]
36#[napi(object)]
37pub struct TransformResult {
38    /// The transformed code.
39    ///
40    /// If parsing failed, this will be an empty string.
41    pub code: String,
42
43    /// The source map for the transformed code.
44    ///
45    /// This will be set if {@link TransformOptions#sourcemap} is `true`.
46    pub map: Option<SourceMap>,
47
48    /// The `.d.ts` declaration file for the transformed code. Declarations are
49    /// only generated if `declaration` is set to `true` and a TypeScript file
50    /// is provided.
51    ///
52    /// If parsing failed and `declaration` is set, this will be an empty string.
53    ///
54    /// @see {@link TypeScriptOptions#declaration}
55    /// @see [declaration tsconfig option](https://www.typescriptlang.org/tsconfig/#declaration)
56    pub declaration: Option<String>,
57
58    /// Declaration source map. Only generated if both
59    /// {@link TypeScriptOptions#declaration declaration} and
60    /// {@link TransformOptions#sourcemap sourcemap} are set to `true`.
61    pub declaration_map: Option<SourceMap>,
62
63    /// Helpers used.
64    ///
65    /// @internal
66    ///
67    /// Example:
68    ///
69    /// ```text
70    /// { "_objectSpread": "@oxc-project/runtime/helpers/objectSpread2" }
71    /// ```
72    #[napi(ts_type = "Record<string, string>")]
73    pub helpers_used: FxHashMap<String, String>,
74
75    /// Parse and transformation errors.
76    ///
77    /// Oxc's parser recovers from common syntax errors, meaning that
78    /// transformed code may still be available even if there are errors in this
79    /// list.
80    pub errors: Vec<OxcError>,
81}
82
83/// Options for transforming a JavaScript or TypeScript file.
84///
85/// @see {@link transform}
86#[napi(object)]
87#[derive(Default)]
88pub struct TransformOptions {
89    /// Treat the source text as `js`, `jsx`, `ts`, `tsx`, or `dts`.
90    #[napi(ts_type = "'js' | 'jsx' | 'ts' | 'tsx' | 'dts'")]
91    pub lang: Option<String>,
92
93    /// Treat the source text as `script` or `module` code.
94    #[napi(ts_type = "'script' | 'module' | 'unambiguous' | undefined")]
95    pub source_type: Option<String>,
96
97    /// The current working directory. Used to resolve relative paths in other
98    /// options.
99    pub cwd: Option<String>,
100
101    /// Enable source map generation.
102    ///
103    /// When `true`, the `sourceMap` field of transform result objects will be populated.
104    ///
105    /// @default false
106    ///
107    /// @see {@link SourceMap}
108    pub sourcemap: Option<bool>,
109
110    /// Set assumptions in order to produce smaller output.
111    pub assumptions: Option<CompilerAssumptions>,
112
113    /// Configure how TypeScript is transformed.
114    pub typescript: Option<TypeScriptOptions>,
115
116    /// Configure how TSX and JSX are transformed.
117    #[napi(ts_type = "'preserve' | JsxOptions")]
118    pub jsx: Option<Either<String, JsxOptions>>,
119
120    /// Sets the target environment for the generated JavaScript.
121    ///
122    /// The lowest target is `es2015`.
123    ///
124    /// Example:
125    ///
126    /// * `'es2015'`
127    /// * `['es2020', 'chrome58', 'edge16', 'firefox57', 'node12', 'safari11']`
128    ///
129    /// @default `esnext` (No transformation)
130    ///
131    /// @see [esbuild#target](https://esbuild.github.io/api/#target)
132    pub target: Option<Either<String, Vec<String>>>,
133
134    /// Behaviour for runtime helpers.
135    pub helpers: Option<Helpers>,
136
137    /// Define Plugin
138    #[napi(ts_type = "Record<string, string>")]
139    pub define: Option<FxHashMap<String, String>>,
140
141    /// Inject Plugin
142    #[napi(ts_type = "Record<string, string | [string, string]>")]
143    pub inject: Option<FxHashMap<String, Either<String, Vec<String>>>>,
144
145    /// Decorator plugin
146    pub decorator: Option<DecoratorOptions>,
147
148    /// Third-party plugins to use.
149    pub plugins: Option<PluginsOptions>,
150}
151
152impl TryFrom<TransformOptions> for oxc::transformer::TransformOptions {
153    type Error = String;
154
155    fn try_from(options: TransformOptions) -> Result<Self, Self::Error> {
156        let env = match options.target {
157            Some(Either::A(s)) => EnvOptions::from_target(&s)?,
158            Some(Either::B(list)) => EnvOptions::from_target_list(&list)?,
159            _ => EnvOptions::default(),
160        };
161        Ok(Self {
162            cwd: options.cwd.map(PathBuf::from).unwrap_or_default(),
163            assumptions: options.assumptions.map(Into::into).unwrap_or_default(),
164            typescript: options
165                .typescript
166                .map(oxc::transformer::TypeScriptOptions::from)
167                .unwrap_or_default(),
168            decorator: options
169                .decorator
170                .map(oxc::transformer::DecoratorOptions::from)
171                .unwrap_or_default(),
172            jsx: match options.jsx {
173                Some(Either::A(s)) => {
174                    if s == "preserve" {
175                        oxc::transformer::JsxOptions::disable()
176                    } else {
177                        return Err(format!("Invalid jsx option: `{s}`."));
178                    }
179                }
180                Some(Either::B(options)) => oxc::transformer::JsxOptions::from(options),
181                None => oxc::transformer::JsxOptions::enable(),
182            },
183            env,
184            proposals: ProposalOptions::default(),
185            helper_loader: options
186                .helpers
187                .map_or_else(HelperLoaderOptions::default, HelperLoaderOptions::from),
188            plugins: options
189                .plugins
190                .map(oxc::transformer::PluginsOptions::from)
191                .unwrap_or_default(),
192        })
193    }
194}
195
196#[napi(object)]
197#[derive(Default, Debug)]
198pub struct CompilerAssumptions {
199    pub ignore_function_length: Option<bool>,
200    pub no_document_all: Option<bool>,
201    pub object_rest_no_symbols: Option<bool>,
202    pub pure_getters: Option<bool>,
203    /// When using public class fields, assume that they don't shadow any getter in the current class,
204    /// in its subclasses or in its superclass. Thus, it's safe to assign them rather than using
205    /// `Object.defineProperty`.
206    ///
207    /// For example:
208    ///
209    /// Input:
210    /// ```js
211    /// class Test {
212    ///  field = 2;
213    ///
214    ///  static staticField = 3;
215    /// }
216    /// ```
217    ///
218    /// When `set_public_class_fields` is `true`, the output will be:
219    /// ```js
220    /// class Test {
221    ///  constructor() {
222    ///    this.field = 2;
223    ///  }
224    /// }
225    /// Test.staticField = 3;
226    /// ```
227    ///
228    /// Otherwise, the output will be:
229    /// ```js
230    /// import _defineProperty from "@oxc-project/runtime/helpers/defineProperty";
231    /// class Test {
232    ///   constructor() {
233    ///     _defineProperty(this, "field", 2);
234    ///   }
235    /// }
236    /// _defineProperty(Test, "staticField", 3);
237    /// ```
238    ///
239    /// NOTE: For TypeScript, if you wanted behavior is equivalent to `useDefineForClassFields: false`, you should
240    /// set both `set_public_class_fields` and [`crate::TypeScriptOptions::remove_class_fields_without_initializer`]
241    /// to `true`.
242    pub set_public_class_fields: Option<bool>,
243}
244
245impl From<CompilerAssumptions> for oxc::transformer::CompilerAssumptions {
246    fn from(value: CompilerAssumptions) -> Self {
247        let ops = oxc::transformer::CompilerAssumptions::default();
248        Self {
249            ignore_function_length: value
250                .ignore_function_length
251                .unwrap_or(ops.ignore_function_length),
252            no_document_all: value.no_document_all.unwrap_or(ops.no_document_all),
253            object_rest_no_symbols: value
254                .object_rest_no_symbols
255                .unwrap_or(ops.object_rest_no_symbols),
256            pure_getters: value.pure_getters.unwrap_or(ops.pure_getters),
257            set_public_class_fields: value
258                .set_public_class_fields
259                .unwrap_or(ops.set_public_class_fields),
260            ..ops
261        }
262    }
263}
264
265#[napi(object)]
266#[derive(Default)]
267pub struct TypeScriptOptions {
268    pub jsx_pragma: Option<String>,
269    pub jsx_pragma_frag: Option<String>,
270    pub only_remove_type_imports: Option<bool>,
271    pub allow_namespaces: Option<bool>,
272    /// When enabled, type-only class fields are only removed if they are prefixed with the declare modifier:
273    ///
274    /// @deprecated
275    ///
276    /// Allowing `declare` fields is built-in support in Oxc without any option. If you want to remove class fields
277    /// without initializer, you can use `remove_class_fields_without_initializer: true` instead.
278    pub allow_declare_fields: Option<bool>,
279    /// When enabled, class fields without initializers are removed.
280    ///
281    /// For example:
282    /// ```ts
283    /// class Foo {
284    ///    x: number;
285    ///    y: number = 0;
286    /// }
287    /// ```
288    /// // transform into
289    /// ```js
290    /// class Foo {
291    ///    x: number;
292    /// }
293    /// ```
294    ///
295    /// The option is used to align with the behavior of TypeScript's `useDefineForClassFields: false` option.
296    /// When you want to enable this, you also need to set [`crate::CompilerAssumptions::set_public_class_fields`]
297    /// to `true`. The `set_public_class_fields: true` + `remove_class_fields_without_initializer: true` is
298    /// equivalent to `useDefineForClassFields: false` in TypeScript.
299    ///
300    /// When `set_public_class_fields` is true and class-properties plugin is enabled, the above example transforms into:
301    ///
302    /// ```js
303    /// class Foo {
304    ///   constructor() {
305    ///     this.y = 0;
306    ///   }
307    /// }
308    /// ```
309    ///
310    /// Defaults to `false`.
311    pub remove_class_fields_without_initializer: Option<bool>,
312    /// Also generate a `.d.ts` declaration file for TypeScript files.
313    ///
314    /// The source file must be compliant with all
315    /// [`isolatedDeclarations`](https://www.typescriptlang.org/docs/handbook/release-notes/typescript-5-5.html#isolated-declarations)
316    /// requirements.
317    ///
318    /// @default false
319    pub declaration: Option<IsolatedDeclarationsOptions>,
320    /// Rewrite or remove TypeScript import/export declaration extensions.
321    ///
322    /// - When set to `rewrite`, it will change `.ts`, `.mts`, `.cts` extensions to `.js`, `.mjs`, `.cjs` respectively.
323    /// - When set to `remove`, it will remove `.ts`/`.mts`/`.cts`/`.tsx` extension entirely.
324    /// - When set to `true`, it's equivalent to `rewrite`.
325    /// - When set to `false` or omitted, no changes will be made to the extensions.
326    ///
327    /// @default false
328    #[napi(ts_type = "'rewrite' | 'remove' | boolean")]
329    pub rewrite_import_extensions: Option<Either<bool, String>>,
330}
331
332impl From<TypeScriptOptions> for oxc::transformer::TypeScriptOptions {
333    fn from(options: TypeScriptOptions) -> Self {
334        let ops = oxc::transformer::TypeScriptOptions::default();
335        oxc::transformer::TypeScriptOptions {
336            jsx_pragma: options.jsx_pragma.map(Into::into).unwrap_or(ops.jsx_pragma),
337            jsx_pragma_frag: options.jsx_pragma_frag.map(Into::into).unwrap_or(ops.jsx_pragma_frag),
338            only_remove_type_imports: options
339                .only_remove_type_imports
340                .unwrap_or(ops.only_remove_type_imports),
341            allow_namespaces: options.allow_namespaces.unwrap_or(ops.allow_namespaces),
342            allow_declare_fields: options.allow_declare_fields.unwrap_or(ops.allow_declare_fields),
343            optimize_const_enums: false,
344            remove_class_fields_without_initializer: options
345                .remove_class_fields_without_initializer
346                .unwrap_or(ops.remove_class_fields_without_initializer),
347            rewrite_import_extensions: options.rewrite_import_extensions.and_then(|value| {
348                match value {
349                    Either::A(v) => {
350                        if v {
351                            Some(RewriteExtensionsMode::Rewrite)
352                        } else {
353                            None
354                        }
355                    }
356                    Either::B(v) => match v.as_str() {
357                        "rewrite" => Some(RewriteExtensionsMode::Rewrite),
358                        "remove" => Some(RewriteExtensionsMode::Remove),
359                        _ => None,
360                    },
361                }
362            }),
363        }
364    }
365}
366
367#[napi(object)]
368#[derive(Default)]
369pub struct DecoratorOptions {
370    /// Enables experimental support for decorators, which is a version of decorators that predates the TC39 standardization process.
371    ///
372    /// Decorators are a language feature which hasn’t yet been fully ratified into the JavaScript specification.
373    /// This means that the implementation version in TypeScript may differ from the implementation in JavaScript when it it decided by TC39.
374    ///
375    /// @see https://www.typescriptlang.org/tsconfig/#experimentalDecorators
376    /// @default false
377    pub legacy: Option<bool>,
378
379    /// Enables emitting decorator metadata.
380    ///
381    /// This option the same as [emitDecoratorMetadata](https://www.typescriptlang.org/tsconfig/#emitDecoratorMetadata)
382    /// in TypeScript, and it only works when `legacy` is true.
383    ///
384    /// @see https://www.typescriptlang.org/tsconfig/#emitDecoratorMetadata
385    /// @default false
386    pub emit_decorator_metadata: Option<bool>,
387}
388
389impl From<DecoratorOptions> for oxc::transformer::DecoratorOptions {
390    fn from(options: DecoratorOptions) -> Self {
391        oxc::transformer::DecoratorOptions {
392            legacy: options.legacy.unwrap_or_default(),
393            emit_decorator_metadata: options.emit_decorator_metadata.unwrap_or_default(),
394        }
395    }
396}
397
398/// Configure how styled-components are transformed.
399///
400/// @see {@link https://styled-components.com/docs/tooling#babel-plugin}
401#[napi(object)]
402#[derive(Default)]
403pub struct StyledComponentsOptions {
404    /// Enhances the attached CSS class name on each component with richer output to help
405    /// identify your components in the DOM without React DevTools.
406    ///
407    /// @default true
408    pub display_name: Option<bool>,
409
410    /// Controls whether the `displayName` of a component will be prefixed with the filename
411    /// to make the component name as unique as possible.
412    ///
413    /// @default true
414    pub file_name: Option<bool>,
415
416    /// Adds a unique identifier to every styled component to avoid checksum mismatches
417    /// due to different class generation on the client and server during server-side rendering.
418    ///
419    /// @default true
420    pub ssr: Option<bool>,
421
422    /// Transpiles styled-components tagged template literals to a smaller representation
423    /// than what Babel normally creates, helping to reduce bundle size.
424    ///
425    /// @default true
426    pub transpile_template_literals: Option<bool>,
427
428    /// Minifies CSS content by removing all whitespace and comments from your CSS,
429    /// keeping valuable bytes out of your bundles.
430    ///
431    /// @default true
432    pub minify: Option<bool>,
433
434    /// Enables transformation of JSX `css` prop when using styled-components.
435    ///
436    /// **Note: This feature is not yet implemented in oxc.**
437    ///
438    /// @default true
439    pub css_prop: Option<bool>,
440
441    /// Enables "pure annotation" to aid dead code elimination by bundlers.
442    ///
443    /// @default false
444    pub pure: Option<bool>,
445
446    /// Adds a namespace prefix to component identifiers to ensure class names are unique.
447    ///
448    /// Example: With `namespace: "my-app"`, generates `componentId: "my-app__sc-3rfj0a-1"`
449    pub namespace: Option<String>,
450
451    /// List of file names that are considered meaningless for component naming purposes.
452    ///
453    /// When the `fileName` option is enabled and a component is in a file with a name
454    /// from this list, the directory name will be used instead of the file name for
455    /// the component's display name.
456    ///
457    /// @default `["index"]`
458    pub meaningless_file_names: Option<Vec<String>>,
459
460    /// Import paths to be considered as styled-components imports at the top level.
461    ///
462    /// **Note: This feature is not yet implemented in oxc.**
463    pub top_level_import_paths: Option<Vec<String>>,
464}
465
466#[napi(object)]
467#[derive(Default)]
468pub struct PluginsOptions {
469    pub styled_components: Option<StyledComponentsOptions>,
470    pub tagged_template_escape: Option<bool>,
471}
472
473impl From<PluginsOptions> for oxc::transformer::PluginsOptions {
474    fn from(options: PluginsOptions) -> Self {
475        oxc::transformer::PluginsOptions {
476            styled_components: options
477                .styled_components
478                .map(oxc::transformer::StyledComponentsOptions::from),
479            tagged_template_transform: options.tagged_template_escape.unwrap_or(false),
480        }
481    }
482}
483
484impl From<StyledComponentsOptions> for oxc::transformer::StyledComponentsOptions {
485    fn from(options: StyledComponentsOptions) -> Self {
486        let ops = oxc::transformer::StyledComponentsOptions::default();
487        oxc::transformer::StyledComponentsOptions {
488            display_name: options.display_name.unwrap_or(ops.display_name),
489            file_name: options.file_name.unwrap_or(ops.file_name),
490            ssr: options.ssr.unwrap_or(ops.ssr),
491            transpile_template_literals: options
492                .transpile_template_literals
493                .unwrap_or(ops.transpile_template_literals),
494            minify: options.minify.unwrap_or(ops.minify),
495            css_prop: options.css_prop.unwrap_or(ops.css_prop),
496            pure: options.pure.unwrap_or(ops.pure),
497            namespace: options.namespace,
498            meaningless_file_names: options
499                .meaningless_file_names
500                .unwrap_or(ops.meaningless_file_names),
501            top_level_import_paths: options
502                .top_level_import_paths
503                .unwrap_or(ops.top_level_import_paths),
504        }
505    }
506}
507
508/// Configure how TSX and JSX are transformed.
509///
510/// @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx#options}
511#[napi(object)]
512pub struct JsxOptions {
513    /// Decides which runtime to use.
514    ///
515    /// - 'automatic' - auto-import the correct JSX factories
516    /// - 'classic' - no auto-import
517    ///
518    /// @default 'automatic'
519    #[napi(ts_type = "'classic' | 'automatic'")]
520    pub runtime: Option<String>,
521
522    /// Emit development-specific information, such as `__source` and `__self`.
523    ///
524    /// @default false
525    ///
526    /// @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx-development}
527    pub development: Option<bool>,
528
529    /// Toggles whether or not to throw an error if an XML namespaced tag name
530    /// is used.
531    ///
532    /// Though the JSX spec allows this, it is disabled by default since React's
533    /// JSX does not currently have support for it.
534    ///
535    /// @default true
536    pub throw_if_namespace: Option<bool>,
537
538    /// Enables `@babel/plugin-transform-react-pure-annotations`.
539    ///
540    /// It will mark JSX elements and top-level React method calls as pure for tree shaking.
541    ///
542    /// @see {@link https://babeljs.io/docs/en/babel-plugin-transform-react-pure-annotations}
543    ///
544    /// @default true
545    pub pure: Option<bool>,
546
547    /// Replaces the import source when importing functions.
548    ///
549    /// @default 'react'
550    pub import_source: Option<String>,
551
552    /// Replace the function used when compiling JSX expressions. It should be a
553    /// qualified name (e.g. `React.createElement`) or an identifier (e.g.
554    /// `createElement`).
555    ///
556    /// Only used for `classic` {@link runtime}.
557    ///
558    /// @default 'React.createElement'
559    pub pragma: Option<String>,
560
561    /// Replace the component used when compiling JSX fragments. It should be a
562    /// valid JSX tag name.
563    ///
564    /// Only used for `classic` {@link runtime}.
565    ///
566    /// @default 'React.Fragment'
567    pub pragma_frag: Option<String>,
568
569    /// When spreading props, use `Object.assign` directly instead of an extend helper.
570    ///
571    /// Only used for `classic` {@link runtime}.
572    ///
573    /// @default false
574    pub use_built_ins: Option<bool>,
575
576    /// When spreading props, use inline object with spread elements directly
577    /// instead of an extend helper or Object.assign.
578    ///
579    /// Only used for `classic` {@link runtime}.
580    ///
581    /// @default false
582    pub use_spread: Option<bool>,
583
584    /// Enable React Fast Refresh .
585    ///
586    /// Conforms to the implementation in {@link https://github.com/facebook/react/tree/v18.3.1/packages/react-refresh}
587    ///
588    /// @default false
589    pub refresh: Option<Either<bool, ReactRefreshOptions>>,
590}
591
592impl From<JsxOptions> for oxc::transformer::JsxOptions {
593    fn from(options: JsxOptions) -> Self {
594        let ops = oxc::transformer::JsxOptions::default();
595        oxc::transformer::JsxOptions {
596            runtime: match options.runtime.as_deref() {
597                Some("classic") => JsxRuntime::Classic,
598                /* "automatic" */ _ => JsxRuntime::Automatic,
599            },
600            development: options.development.unwrap_or(ops.development),
601            throw_if_namespace: options.throw_if_namespace.unwrap_or(ops.throw_if_namespace),
602            pure: options.pure.unwrap_or(ops.pure),
603            import_source: options.import_source,
604            pragma: options.pragma,
605            pragma_frag: options.pragma_frag,
606            use_built_ins: options.use_built_ins,
607            use_spread: options.use_spread,
608            refresh: options.refresh.and_then(|value| match value {
609                Either::A(b) => b.then(oxc::transformer::ReactRefreshOptions::default),
610                Either::B(options) => Some(oxc::transformer::ReactRefreshOptions::from(options)),
611            }),
612            ..Default::default()
613        }
614    }
615}
616
617#[napi(object)]
618pub struct ReactRefreshOptions {
619    /// Specify the identifier of the refresh registration variable.
620    ///
621    /// @default `$RefreshReg$`.
622    pub refresh_reg: Option<String>,
623
624    /// Specify the identifier of the refresh signature variable.
625    ///
626    /// @default `$RefreshSig$`.
627    pub refresh_sig: Option<String>,
628
629    pub emit_full_signatures: Option<bool>,
630}
631
632impl From<ReactRefreshOptions> for oxc::transformer::ReactRefreshOptions {
633    fn from(options: ReactRefreshOptions) -> Self {
634        let ops = oxc::transformer::ReactRefreshOptions::default();
635        oxc::transformer::ReactRefreshOptions {
636            refresh_reg: options.refresh_reg.unwrap_or(ops.refresh_reg),
637            refresh_sig: options.refresh_sig.unwrap_or(ops.refresh_sig),
638            emit_full_signatures: options.emit_full_signatures.unwrap_or(ops.emit_full_signatures),
639        }
640    }
641}
642
643#[napi(object)]
644pub struct ArrowFunctionsOptions {
645    /// This option enables the following:
646    /// * Wrap the generated function in .bind(this) and keeps uses of this inside the function as-is, instead of using a renamed this.
647    /// * Add a runtime check to ensure the functions are not instantiated.
648    /// * Add names to arrow functions.
649    ///
650    /// @default false
651    pub spec: Option<bool>,
652}
653
654impl From<ArrowFunctionsOptions> for oxc::transformer::ArrowFunctionsOptions {
655    fn from(options: ArrowFunctionsOptions) -> Self {
656        oxc::transformer::ArrowFunctionsOptions { spec: options.spec.unwrap_or_default() }
657    }
658}
659
660#[napi(object)]
661pub struct Es2015Options {
662    /// Transform arrow functions into function expressions.
663    pub arrow_function: Option<ArrowFunctionsOptions>,
664}
665
666impl From<Es2015Options> for oxc::transformer::ES2015Options {
667    fn from(options: Es2015Options) -> Self {
668        oxc::transformer::ES2015Options { arrow_function: options.arrow_function.map(Into::into) }
669    }
670}
671
672#[napi(object)]
673#[derive(Default)]
674pub struct Helpers {
675    pub mode: Option<HelperMode>,
676}
677
678#[derive(Default, Clone, Copy)]
679#[napi(string_enum)]
680pub enum HelperMode {
681    /// Runtime mode (default): Helper functions are imported from a runtime package.
682    ///
683    /// Example:
684    ///
685    /// ```js
686    /// import helperName from "@oxc-project/runtime/helpers/helperName";
687    /// helperName(...arguments);
688    /// ```
689    #[default]
690    Runtime,
691    /// External mode: Helper functions are accessed from a global `babelHelpers` object.
692    ///
693    /// Example:
694    ///
695    /// ```js
696    /// babelHelpers.helperName(...arguments);
697    /// ```
698    External,
699}
700
701impl From<Helpers> for HelperLoaderOptions {
702    fn from(value: Helpers) -> Self {
703        Self {
704            mode: value.mode.map(HelperLoaderMode::from).unwrap_or_default(),
705            ..HelperLoaderOptions::default()
706        }
707    }
708}
709
710impl From<HelperMode> for HelperLoaderMode {
711    fn from(value: HelperMode) -> Self {
712        match value {
713            HelperMode::Runtime => Self::Runtime,
714            HelperMode::External => Self::External,
715        }
716    }
717}
718
719#[derive(Default)]
720struct Compiler {
721    transform_options: oxc::transformer::TransformOptions,
722    isolated_declaration_options: Option<oxc::isolated_declarations::IsolatedDeclarationsOptions>,
723
724    sourcemap: bool,
725
726    printed: String,
727    printed_sourcemap: Option<SourceMap>,
728
729    declaration: Option<String>,
730    declaration_map: Option<SourceMap>,
731
732    define: Option<ReplaceGlobalDefinesConfig>,
733    inject: Option<InjectGlobalVariablesConfig>,
734
735    helpers_used: FxHashMap<String, String>,
736    errors: Vec<OxcDiagnostic>,
737}
738
739impl Compiler {
740    fn new(options: Option<TransformOptions>) -> Result<Self, Vec<OxcDiagnostic>> {
741        let mut options = options;
742
743        let isolated_declaration_options = options
744            .as_ref()
745            .and_then(|o| o.typescript.as_ref())
746            .and_then(|o| o.declaration)
747            .map(oxc::isolated_declarations::IsolatedDeclarationsOptions::from);
748
749        let sourcemap = options.as_ref().and_then(|o| o.sourcemap).unwrap_or_default();
750
751        let define = options
752            .as_mut()
753            .and_then(|options| options.define.take())
754            .map(|map| {
755                let define = map.into_iter().collect::<Vec<_>>();
756                ReplaceGlobalDefinesConfig::new(&define)
757            })
758            .transpose()?;
759
760        let inject = options
761            .as_mut()
762            .and_then(|options| options.inject.take())
763            .map(|map| {
764                map.into_iter()
765                    .map(|(local, value)| match value {
766                        Either::A(source) => Ok(InjectImport::default_specifier(&source, &local)),
767                        Either::B(v) => {
768                            if v.len() != 2 {
769                                return Err(vec![OxcDiagnostic::error(
770                                    "Inject plugin did not receive a tuple [string, string].",
771                                )]);
772                            }
773                            let source = &v[0];
774                            Ok(if v[1] == "*" {
775                                InjectImport::namespace_specifier(source, &local)
776                            } else {
777                                InjectImport::named_specifier(source, Some(&v[1]), &local)
778                            })
779                        }
780                    })
781                    .collect::<Result<Vec<_>, _>>()
782            })
783            .transpose()?
784            .map(InjectGlobalVariablesConfig::new);
785
786        let transform_options = match options {
787            Some(options) => oxc::transformer::TransformOptions::try_from(options)
788                .map_err(|err| vec![OxcDiagnostic::error(err)])?,
789            None => oxc::transformer::TransformOptions::default(),
790        };
791
792        Ok(Self {
793            transform_options,
794            isolated_declaration_options,
795            sourcemap,
796            printed: String::default(),
797            printed_sourcemap: None,
798            declaration: None,
799            declaration_map: None,
800            define,
801            inject,
802            helpers_used: FxHashMap::default(),
803            errors: vec![],
804        })
805    }
806}
807
808impl CompilerInterface for Compiler {
809    fn handle_errors(&mut self, errors: Vec<OxcDiagnostic>) {
810        self.errors.extend(errors);
811    }
812
813    fn enable_sourcemap(&self) -> bool {
814        self.sourcemap
815    }
816
817    fn transform_options(&self) -> Option<&oxc::transformer::TransformOptions> {
818        Some(&self.transform_options)
819    }
820
821    fn isolated_declaration_options(
822        &self,
823    ) -> Option<oxc::isolated_declarations::IsolatedDeclarationsOptions> {
824        self.isolated_declaration_options
825    }
826
827    fn define_options(&self) -> Option<ReplaceGlobalDefinesConfig> {
828        self.define.clone()
829    }
830
831    fn inject_options(&self) -> Option<InjectGlobalVariablesConfig> {
832        self.inject.clone()
833    }
834
835    fn after_codegen(&mut self, ret: CodegenReturn) {
836        self.printed = ret.code;
837        self.printed_sourcemap = ret.map.map(SourceMap::from);
838    }
839
840    fn after_isolated_declarations(&mut self, ret: CodegenReturn) {
841        self.declaration.replace(ret.code);
842        self.declaration_map = ret.map.map(SourceMap::from);
843    }
844
845    #[expect(deprecated)]
846    fn after_transform(
847        &mut self,
848        _program: &mut oxc::ast::ast::Program<'_>,
849        transformer_return: &mut oxc::transformer::TransformerReturn,
850    ) -> ControlFlow<()> {
851        self.helpers_used = transformer_return
852            .helpers_used
853            .drain()
854            .map(|(helper, source)| (helper.name().to_string(), source))
855            .collect();
856        ControlFlow::Continue(())
857    }
858}
859
860/// Transpile a JavaScript or TypeScript into a target ECMAScript version.
861///
862/// @param filename The name of the file being transformed. If this is a
863/// relative path, consider setting the {@link TransformOptions#cwd} option..
864/// @param sourceText the source code itself
865/// @param options The options for the transformation. See {@link
866/// TransformOptions} for more information.
867///
868/// @returns an object containing the transformed code, source maps, and any
869/// errors that occurred during parsing or transformation.
870#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
871#[napi]
872pub fn transform_sync(
873    filename: String,
874    source_text: String,
875    options: Option<TransformOptions>,
876) -> TransformResult {
877    let source_path = Path::new(&filename);
878
879    let source_type = get_source_type(
880        &filename,
881        options.as_ref().and_then(|options| options.lang.as_deref()),
882        options.as_ref().and_then(|options| options.source_type.as_deref()),
883    );
884
885    let mut compiler = match Compiler::new(options) {
886        Ok(compiler) => compiler,
887        Err(errors) => {
888            return TransformResult {
889                errors: OxcError::from_diagnostics(&filename, &source_text, errors),
890                ..Default::default()
891            };
892        }
893    };
894
895    compiler.compile(&source_text, source_type, source_path);
896
897    TransformResult {
898        code: compiler.printed,
899        map: compiler.printed_sourcemap,
900        declaration: compiler.declaration,
901        declaration_map: compiler.declaration_map,
902        helpers_used: compiler.helpers_used,
903        errors: OxcError::from_diagnostics(&filename, &source_text, compiler.errors),
904    }
905}
906
907pub struct TransformTask {
908    filename: String,
909    source_text: String,
910    options: Option<TransformOptions>,
911}
912
913#[napi]
914impl Task for TransformTask {
915    type JsValue = TransformResult;
916    type Output = TransformResult;
917
918    fn compute(&mut self) -> napi::Result<Self::Output> {
919        let source_path = Path::new(&self.filename);
920
921        let source_type = get_source_type(
922            &self.filename,
923            self.options.as_ref().and_then(|options| options.lang.as_deref()),
924            self.options.as_ref().and_then(|options| options.source_type.as_deref()),
925        );
926
927        let mut compiler = match Compiler::new(self.options.take()) {
928            Ok(compiler) => compiler,
929            Err(errors) => {
930                return Ok(TransformResult {
931                    errors: OxcError::from_diagnostics(&self.filename, &self.source_text, errors),
932                    ..Default::default()
933                });
934            }
935        };
936
937        compiler.compile(&self.source_text, source_type, source_path);
938
939        Ok(TransformResult {
940            code: compiler.printed,
941            map: compiler.printed_sourcemap,
942            declaration: compiler.declaration,
943            declaration_map: compiler.declaration_map,
944            helpers_used: compiler.helpers_used,
945            errors: OxcError::from_diagnostics(&self.filename, &self.source_text, compiler.errors),
946        })
947    }
948
949    fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
950        Ok(result)
951    }
952}
953
954/// Transpile a JavaScript or TypeScript into a target ECMAScript version, asynchronously.
955///
956/// Note: This function can be slower than `transform` due to the overhead of spawning a thread.
957///
958/// @param filename The name of the file being transformed. If this is a
959/// relative path, consider setting the {@link TransformOptions#cwd} option.
960/// @param sourceText the source code itself
961/// @param options The options for the transformation. See {@link
962/// TransformOptions} for more information.
963///
964/// @returns a promise that resolves to an object containing the transformed code,
965/// source maps, and any errors that occurred during parsing or transformation.
966#[napi]
967pub fn transform(
968    filename: String,
969    source_text: String,
970    options: Option<TransformOptions>,
971) -> AsyncTask<TransformTask> {
972    AsyncTask::new(TransformTask { filename, source_text, options })
973}
974
975#[derive(Default)]
976#[napi(object)]
977pub struct ModuleRunnerTransformOptions {
978    /// Enable source map generation.
979    ///
980    /// When `true`, the `sourceMap` field of transform result objects will be populated.
981    ///
982    /// @default false
983    ///
984    /// @see {@link SourceMap}
985    pub sourcemap: Option<bool>,
986}
987
988#[derive(Default)]
989#[napi(object)]
990pub struct ModuleRunnerTransformResult {
991    /// The transformed code.
992    ///
993    /// If parsing failed, this will be an empty string.
994    pub code: String,
995
996    /// The source map for the transformed code.
997    ///
998    /// This will be set if {@link TransformOptions#sourcemap} is `true`.
999    pub map: Option<SourceMap>,
1000
1001    // Import sources collected during transformation.
1002    pub deps: Vec<String>,
1003
1004    // Dynamic import sources collected during transformation.
1005    pub dynamic_deps: Vec<String>,
1006
1007    /// Parse and transformation errors.
1008    ///
1009    /// Oxc's parser recovers from common syntax errors, meaning that
1010    /// transformed code may still be available even if there are errors in this
1011    /// list.
1012    pub errors: Vec<OxcError>,
1013}
1014
1015/// Transform JavaScript code to a Vite Node runnable module.
1016///
1017/// @param filename The name of the file being transformed.
1018/// @param sourceText the source code itself
1019/// @param options The options for the transformation. See {@link
1020/// ModuleRunnerTransformOptions} for more information.
1021///
1022/// @returns an object containing the transformed code, source maps, and any
1023/// errors that occurred during parsing or transformation.
1024///
1025fn module_runner_transform_impl(
1026    filename: &str,
1027    source_text: &str,
1028    options: Option<ModuleRunnerTransformOptions>,
1029) -> ModuleRunnerTransformResult {
1030    let file_path = Path::new(filename);
1031    let source_type = SourceType::from_path(file_path);
1032    let source_type = match source_type {
1033        Ok(s) => s,
1034        Err(err) => {
1035            return ModuleRunnerTransformResult {
1036                code: String::default(),
1037                map: None,
1038                deps: vec![],
1039                dynamic_deps: vec![],
1040                errors: vec![OxcError::new(err.to_string())],
1041            };
1042        }
1043    };
1044
1045    let allocator = Allocator::default();
1046    let mut parser_ret = Parser::new(&allocator, source_text, source_type).parse();
1047    let mut program = parser_ret.program;
1048
1049    let SemanticBuilderReturn { semantic, errors } =
1050        SemanticBuilder::new().with_check_syntax_error(true).build(&program);
1051    parser_ret.errors.extend(errors);
1052
1053    let scoping = semantic.into_scoping();
1054    let (deps, dynamic_deps) =
1055        ModuleRunnerTransform::default().transform(&allocator, &mut program, scoping);
1056
1057    let CodegenReturn { code, map, .. } = Codegen::new()
1058        .with_options(CodegenOptions {
1059            source_map_path: options.and_then(|opts| {
1060                opts.sourcemap.as_ref().and_then(|s| s.then(|| file_path.to_path_buf()))
1061            }),
1062            ..Default::default()
1063        })
1064        .build(&program);
1065
1066    ModuleRunnerTransformResult {
1067        code,
1068        map: map.map(Into::into),
1069        deps: deps.into_iter().collect::<Vec<String>>(),
1070        dynamic_deps: dynamic_deps.into_iter().collect::<Vec<String>>(),
1071        errors: OxcError::from_diagnostics(filename, source_text, parser_ret.errors),
1072    }
1073}
1074
1075/// @deprecated Only works for Vite.
1076#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
1077#[napi]
1078pub fn module_runner_transform_sync(
1079    filename: String,
1080    source_text: String,
1081    options: Option<ModuleRunnerTransformOptions>,
1082) -> ModuleRunnerTransformResult {
1083    module_runner_transform_impl(&filename, &source_text, options)
1084}
1085
1086pub struct ModuleRunnerTransformTask {
1087    filename: String,
1088    source_text: String,
1089    options: Option<ModuleRunnerTransformOptions>,
1090}
1091
1092#[napi]
1093impl Task for ModuleRunnerTransformTask {
1094    type JsValue = ModuleRunnerTransformResult;
1095    type Output = ModuleRunnerTransformResult;
1096
1097    fn compute(&mut self) -> napi::Result<Self::Output> {
1098        Ok(module_runner_transform_impl(&self.filename, &self.source_text, self.options.take()))
1099    }
1100
1101    fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
1102        Ok(result)
1103    }
1104}
1105
1106/// Transform JavaScript code to a Vite Node runnable module.
1107///
1108/// @param filename The name of the file being transformed.
1109/// @param sourceText the source code itself
1110/// @param options The options for the transformation. See {@link
1111/// ModuleRunnerTransformOptions} for more information.
1112///
1113/// @returns an object containing the transformed code, source maps, and any
1114/// errors that occurred during parsing or transformation.
1115///
1116/// Note: This function can be slower than `moduleRunnerTransformSync` due to the overhead of spawning a thread.
1117///
1118/// @deprecated Only works for Vite.
1119#[napi]
1120pub fn module_runner_transform(
1121    filename: String,
1122    source_text: String,
1123    options: Option<ModuleRunnerTransformOptions>,
1124) -> AsyncTask<ModuleRunnerTransformTask> {
1125    AsyncTask::new(ModuleRunnerTransformTask { filename, source_text, options })
1126}