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}
471
472impl From<PluginsOptions> for oxc::transformer::PluginsOptions {
473    fn from(options: PluginsOptions) -> Self {
474        oxc::transformer::PluginsOptions {
475            styled_components: options
476                .styled_components
477                .map(oxc::transformer::StyledComponentsOptions::from),
478        }
479    }
480}
481
482impl From<StyledComponentsOptions> for oxc::transformer::StyledComponentsOptions {
483    fn from(options: StyledComponentsOptions) -> Self {
484        let ops = oxc::transformer::StyledComponentsOptions::default();
485        oxc::transformer::StyledComponentsOptions {
486            display_name: options.display_name.unwrap_or(ops.display_name),
487            file_name: options.file_name.unwrap_or(ops.file_name),
488            ssr: options.ssr.unwrap_or(ops.ssr),
489            transpile_template_literals: options
490                .transpile_template_literals
491                .unwrap_or(ops.transpile_template_literals),
492            minify: options.minify.unwrap_or(ops.minify),
493            css_prop: options.css_prop.unwrap_or(ops.css_prop),
494            pure: options.pure.unwrap_or(ops.pure),
495            namespace: options.namespace,
496            meaningless_file_names: options
497                .meaningless_file_names
498                .unwrap_or(ops.meaningless_file_names),
499            top_level_import_paths: options
500                .top_level_import_paths
501                .unwrap_or(ops.top_level_import_paths),
502        }
503    }
504}
505
506/// Configure how TSX and JSX are transformed.
507///
508/// @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx#options}
509#[napi(object)]
510pub struct JsxOptions {
511    /// Decides which runtime to use.
512    ///
513    /// - 'automatic' - auto-import the correct JSX factories
514    /// - 'classic' - no auto-import
515    ///
516    /// @default 'automatic'
517    #[napi(ts_type = "'classic' | 'automatic'")]
518    pub runtime: Option<String>,
519
520    /// Emit development-specific information, such as `__source` and `__self`.
521    ///
522    /// @default false
523    ///
524    /// @see {@link https://babeljs.io/docs/babel-plugin-transform-react-jsx-development}
525    pub development: Option<bool>,
526
527    /// Toggles whether or not to throw an error if an XML namespaced tag name
528    /// is used.
529    ///
530    /// Though the JSX spec allows this, it is disabled by default since React's
531    /// JSX does not currently have support for it.
532    ///
533    /// @default true
534    pub throw_if_namespace: Option<bool>,
535
536    /// Enables `@babel/plugin-transform-react-pure-annotations`.
537    ///
538    /// It will mark top-level React method calls as pure for tree shaking.
539    ///
540    /// @see {@link https://babeljs.io/docs/en/babel-plugin-transform-react-pure-annotations}
541    ///
542    /// @default true
543    pub pure: Option<bool>,
544
545    /// Replaces the import source when importing functions.
546    ///
547    /// @default 'react'
548    pub import_source: Option<String>,
549
550    /// Replace the function used when compiling JSX expressions. It should be a
551    /// qualified name (e.g. `React.createElement`) or an identifier (e.g.
552    /// `createElement`).
553    ///
554    /// Only used for `classic` {@link runtime}.
555    ///
556    /// @default 'React.createElement'
557    pub pragma: Option<String>,
558
559    /// Replace the component used when compiling JSX fragments. It should be a
560    /// valid JSX tag name.
561    ///
562    /// Only used for `classic` {@link runtime}.
563    ///
564    /// @default 'React.Fragment'
565    pub pragma_frag: Option<String>,
566
567    /// When spreading props, use `Object.assign` directly instead of an extend helper.
568    ///
569    /// Only used for `classic` {@link runtime}.
570    ///
571    /// @default false
572    pub use_built_ins: Option<bool>,
573
574    /// When spreading props, use inline object with spread elements directly
575    /// instead of an extend helper or Object.assign.
576    ///
577    /// Only used for `classic` {@link runtime}.
578    ///
579    /// @default false
580    pub use_spread: Option<bool>,
581
582    /// Enable React Fast Refresh .
583    ///
584    /// Conforms to the implementation in {@link https://github.com/facebook/react/tree/v18.3.1/packages/react-refresh}
585    ///
586    /// @default false
587    pub refresh: Option<Either<bool, ReactRefreshOptions>>,
588}
589
590impl From<JsxOptions> for oxc::transformer::JsxOptions {
591    fn from(options: JsxOptions) -> Self {
592        let ops = oxc::transformer::JsxOptions::default();
593        oxc::transformer::JsxOptions {
594            runtime: match options.runtime.as_deref() {
595                Some("classic") => JsxRuntime::Classic,
596                /* "automatic" */ _ => JsxRuntime::Automatic,
597            },
598            development: options.development.unwrap_or(ops.development),
599            throw_if_namespace: options.throw_if_namespace.unwrap_or(ops.throw_if_namespace),
600            pure: options.pure.unwrap_or(ops.pure),
601            import_source: options.import_source,
602            pragma: options.pragma,
603            pragma_frag: options.pragma_frag,
604            use_built_ins: options.use_built_ins,
605            use_spread: options.use_spread,
606            refresh: options.refresh.and_then(|value| match value {
607                Either::A(b) => b.then(oxc::transformer::ReactRefreshOptions::default),
608                Either::B(options) => Some(oxc::transformer::ReactRefreshOptions::from(options)),
609            }),
610            ..Default::default()
611        }
612    }
613}
614
615#[napi(object)]
616pub struct ReactRefreshOptions {
617    /// Specify the identifier of the refresh registration variable.
618    ///
619    /// @default `$RefreshReg$`.
620    pub refresh_reg: Option<String>,
621
622    /// Specify the identifier of the refresh signature variable.
623    ///
624    /// @default `$RefreshSig$`.
625    pub refresh_sig: Option<String>,
626
627    pub emit_full_signatures: Option<bool>,
628}
629
630impl From<ReactRefreshOptions> for oxc::transformer::ReactRefreshOptions {
631    fn from(options: ReactRefreshOptions) -> Self {
632        let ops = oxc::transformer::ReactRefreshOptions::default();
633        oxc::transformer::ReactRefreshOptions {
634            refresh_reg: options.refresh_reg.unwrap_or(ops.refresh_reg),
635            refresh_sig: options.refresh_sig.unwrap_or(ops.refresh_sig),
636            emit_full_signatures: options.emit_full_signatures.unwrap_or(ops.emit_full_signatures),
637        }
638    }
639}
640
641#[napi(object)]
642pub struct ArrowFunctionsOptions {
643    /// This option enables the following:
644    /// * Wrap the generated function in .bind(this) and keeps uses of this inside the function as-is, instead of using a renamed this.
645    /// * Add a runtime check to ensure the functions are not instantiated.
646    /// * Add names to arrow functions.
647    ///
648    /// @default false
649    pub spec: Option<bool>,
650}
651
652impl From<ArrowFunctionsOptions> for oxc::transformer::ArrowFunctionsOptions {
653    fn from(options: ArrowFunctionsOptions) -> Self {
654        oxc::transformer::ArrowFunctionsOptions { spec: options.spec.unwrap_or_default() }
655    }
656}
657
658#[napi(object)]
659pub struct Es2015Options {
660    /// Transform arrow functions into function expressions.
661    pub arrow_function: Option<ArrowFunctionsOptions>,
662}
663
664impl From<Es2015Options> for oxc::transformer::ES2015Options {
665    fn from(options: Es2015Options) -> Self {
666        oxc::transformer::ES2015Options { arrow_function: options.arrow_function.map(Into::into) }
667    }
668}
669
670#[napi(object)]
671#[derive(Default)]
672pub struct Helpers {
673    pub mode: Option<HelperMode>,
674}
675
676#[derive(Default, Clone, Copy)]
677#[napi(string_enum)]
678pub enum HelperMode {
679    /// Runtime mode (default): Helper functions are imported from a runtime package.
680    ///
681    /// Example:
682    ///
683    /// ```js
684    /// import helperName from "@oxc-project/runtime/helpers/helperName";
685    /// helperName(...arguments);
686    /// ```
687    #[default]
688    Runtime,
689    /// External mode: Helper functions are accessed from a global `babelHelpers` object.
690    ///
691    /// Example:
692    ///
693    /// ```js
694    /// babelHelpers.helperName(...arguments);
695    /// ```
696    External,
697}
698
699impl From<Helpers> for HelperLoaderOptions {
700    fn from(value: Helpers) -> Self {
701        Self {
702            mode: value.mode.map(HelperLoaderMode::from).unwrap_or_default(),
703            ..HelperLoaderOptions::default()
704        }
705    }
706}
707
708impl From<HelperMode> for HelperLoaderMode {
709    fn from(value: HelperMode) -> Self {
710        match value {
711            HelperMode::Runtime => Self::Runtime,
712            HelperMode::External => Self::External,
713        }
714    }
715}
716
717#[derive(Default)]
718struct Compiler {
719    transform_options: oxc::transformer::TransformOptions,
720    isolated_declaration_options: Option<oxc::isolated_declarations::IsolatedDeclarationsOptions>,
721
722    sourcemap: bool,
723
724    printed: String,
725    printed_sourcemap: Option<SourceMap>,
726
727    declaration: Option<String>,
728    declaration_map: Option<SourceMap>,
729
730    define: Option<ReplaceGlobalDefinesConfig>,
731    inject: Option<InjectGlobalVariablesConfig>,
732
733    helpers_used: FxHashMap<String, String>,
734    errors: Vec<OxcDiagnostic>,
735}
736
737impl Compiler {
738    fn new(options: Option<TransformOptions>) -> Result<Self, Vec<OxcDiagnostic>> {
739        let mut options = options;
740
741        let isolated_declaration_options = options
742            .as_ref()
743            .and_then(|o| o.typescript.as_ref())
744            .and_then(|o| o.declaration)
745            .map(oxc::isolated_declarations::IsolatedDeclarationsOptions::from);
746
747        let sourcemap = options.as_ref().and_then(|o| o.sourcemap).unwrap_or_default();
748
749        let define = options
750            .as_mut()
751            .and_then(|options| options.define.take())
752            .map(|map| {
753                let define = map.into_iter().collect::<Vec<_>>();
754                ReplaceGlobalDefinesConfig::new(&define)
755            })
756            .transpose()?;
757
758        let inject = options
759            .as_mut()
760            .and_then(|options| options.inject.take())
761            .map(|map| {
762                map.into_iter()
763                    .map(|(local, value)| match value {
764                        Either::A(source) => Ok(InjectImport::default_specifier(&source, &local)),
765                        Either::B(v) => {
766                            if v.len() != 2 {
767                                return Err(vec![OxcDiagnostic::error(
768                                    "Inject plugin did not receive a tuple [string, string].",
769                                )]);
770                            }
771                            let source = &v[0];
772                            Ok(if v[1] == "*" {
773                                InjectImport::namespace_specifier(source, &local)
774                            } else {
775                                InjectImport::named_specifier(source, Some(&v[1]), &local)
776                            })
777                        }
778                    })
779                    .collect::<Result<Vec<_>, _>>()
780            })
781            .transpose()?
782            .map(InjectGlobalVariablesConfig::new);
783
784        let transform_options = match options {
785            Some(options) => oxc::transformer::TransformOptions::try_from(options)
786                .map_err(|err| vec![OxcDiagnostic::error(err)])?,
787            None => oxc::transformer::TransformOptions::default(),
788        };
789
790        Ok(Self {
791            transform_options,
792            isolated_declaration_options,
793            sourcemap,
794            printed: String::default(),
795            printed_sourcemap: None,
796            declaration: None,
797            declaration_map: None,
798            define,
799            inject,
800            helpers_used: FxHashMap::default(),
801            errors: vec![],
802        })
803    }
804}
805
806impl CompilerInterface for Compiler {
807    fn handle_errors(&mut self, errors: Vec<OxcDiagnostic>) {
808        self.errors.extend(errors);
809    }
810
811    fn enable_sourcemap(&self) -> bool {
812        self.sourcemap
813    }
814
815    fn transform_options(&self) -> Option<&oxc::transformer::TransformOptions> {
816        Some(&self.transform_options)
817    }
818
819    fn isolated_declaration_options(
820        &self,
821    ) -> Option<oxc::isolated_declarations::IsolatedDeclarationsOptions> {
822        self.isolated_declaration_options
823    }
824
825    fn define_options(&self) -> Option<ReplaceGlobalDefinesConfig> {
826        self.define.clone()
827    }
828
829    fn inject_options(&self) -> Option<InjectGlobalVariablesConfig> {
830        self.inject.clone()
831    }
832
833    fn after_codegen(&mut self, ret: CodegenReturn) {
834        self.printed = ret.code;
835        self.printed_sourcemap = ret.map.map(SourceMap::from);
836    }
837
838    fn after_isolated_declarations(&mut self, ret: CodegenReturn) {
839        self.declaration.replace(ret.code);
840        self.declaration_map = ret.map.map(SourceMap::from);
841    }
842
843    #[expect(deprecated)]
844    fn after_transform(
845        &mut self,
846        _program: &mut oxc::ast::ast::Program<'_>,
847        transformer_return: &mut oxc::transformer::TransformerReturn,
848    ) -> ControlFlow<()> {
849        self.helpers_used = transformer_return
850            .helpers_used
851            .drain()
852            .map(|(helper, source)| (helper.name().to_string(), source))
853            .collect();
854        ControlFlow::Continue(())
855    }
856}
857
858/// Transpile a JavaScript or TypeScript into a target ECMAScript version.
859///
860/// @param filename The name of the file being transformed. If this is a
861/// relative path, consider setting the {@link TransformOptions#cwd} option..
862/// @param sourceText the source code itself
863/// @param options The options for the transformation. See {@link
864/// TransformOptions} for more information.
865///
866/// @returns an object containing the transformed code, source maps, and any
867/// errors that occurred during parsing or transformation.
868#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
869#[napi]
870pub fn transform(
871    filename: String,
872    source_text: String,
873    options: Option<TransformOptions>,
874) -> TransformResult {
875    let source_path = Path::new(&filename);
876
877    let source_type = get_source_type(
878        &filename,
879        options.as_ref().and_then(|options| options.lang.as_deref()),
880        options.as_ref().and_then(|options| options.source_type.as_deref()),
881    );
882
883    let mut compiler = match Compiler::new(options) {
884        Ok(compiler) => compiler,
885        Err(errors) => {
886            return TransformResult {
887                errors: OxcError::from_diagnostics(&filename, &source_text, errors),
888                ..Default::default()
889            };
890        }
891    };
892
893    compiler.compile(&source_text, source_type, source_path);
894
895    TransformResult {
896        code: compiler.printed,
897        map: compiler.printed_sourcemap,
898        declaration: compiler.declaration,
899        declaration_map: compiler.declaration_map,
900        helpers_used: compiler.helpers_used,
901        errors: OxcError::from_diagnostics(&filename, &source_text, compiler.errors),
902    }
903}
904
905pub struct TransformTask {
906    filename: String,
907    source_text: String,
908    options: Option<TransformOptions>,
909}
910
911#[napi]
912impl Task for TransformTask {
913    type JsValue = TransformResult;
914    type Output = TransformResult;
915
916    fn compute(&mut self) -> napi::Result<Self::Output> {
917        let source_path = Path::new(&self.filename);
918
919        let source_type = get_source_type(
920            &self.filename,
921            self.options.as_ref().and_then(|options| options.lang.as_deref()),
922            self.options.as_ref().and_then(|options| options.source_type.as_deref()),
923        );
924
925        let mut compiler = match Compiler::new(self.options.take()) {
926            Ok(compiler) => compiler,
927            Err(errors) => {
928                return Ok(TransformResult {
929                    errors: OxcError::from_diagnostics(&self.filename, &self.source_text, errors),
930                    ..Default::default()
931                });
932            }
933        };
934
935        compiler.compile(&self.source_text, source_type, source_path);
936
937        Ok(TransformResult {
938            code: compiler.printed,
939            map: compiler.printed_sourcemap,
940            declaration: compiler.declaration,
941            declaration_map: compiler.declaration_map,
942            helpers_used: compiler.helpers_used,
943            errors: OxcError::from_diagnostics(&self.filename, &self.source_text, compiler.errors),
944        })
945    }
946
947    fn resolve(&mut self, _: napi::Env, result: Self::Output) -> napi::Result<Self::JsValue> {
948        Ok(result)
949    }
950}
951
952/// Transpile a JavaScript or TypeScript into a target ECMAScript version, asynchronously.
953///
954/// Note: This function can be slower than `transform` due to the overhead of spawning a thread.
955///
956/// @param filename The name of the file being transformed. If this is a
957/// relative path, consider setting the {@link TransformOptions#cwd} option.
958/// @param sourceText the source code itself
959/// @param options The options for the transformation. See {@link
960/// TransformOptions} for more information.
961///
962/// @returns a promise that resolves to an object containing the transformed code,
963/// source maps, and any errors that occurred during parsing or transformation.
964#[napi]
965pub fn transform_async(
966    filename: String,
967    source_text: String,
968    options: Option<TransformOptions>,
969) -> AsyncTask<TransformTask> {
970    AsyncTask::new(TransformTask { filename, source_text, options })
971}
972
973#[derive(Default)]
974#[napi(object)]
975pub struct ModuleRunnerTransformOptions {
976    /// Enable source map generation.
977    ///
978    /// When `true`, the `sourceMap` field of transform result objects will be populated.
979    ///
980    /// @default false
981    ///
982    /// @see {@link SourceMap}
983    pub sourcemap: Option<bool>,
984}
985
986#[derive(Default)]
987#[napi(object)]
988pub struct ModuleRunnerTransformResult {
989    /// The transformed code.
990    ///
991    /// If parsing failed, this will be an empty string.
992    pub code: String,
993
994    /// The source map for the transformed code.
995    ///
996    /// This will be set if {@link TransformOptions#sourcemap} is `true`.
997    pub map: Option<SourceMap>,
998
999    // Import sources collected during transformation.
1000    pub deps: Vec<String>,
1001
1002    // Dynamic import sources collected during transformation.
1003    pub dynamic_deps: Vec<String>,
1004
1005    /// Parse and transformation errors.
1006    ///
1007    /// Oxc's parser recovers from common syntax errors, meaning that
1008    /// transformed code may still be available even if there are errors in this
1009    /// list.
1010    pub errors: Vec<OxcError>,
1011}
1012
1013/// Transform JavaScript code to a Vite Node runnable module.
1014///
1015/// @param filename The name of the file being transformed.
1016/// @param sourceText the source code itself
1017/// @param options The options for the transformation. See {@link
1018/// ModuleRunnerTransformOptions} for more information.
1019///
1020/// @returns an object containing the transformed code, source maps, and any
1021/// errors that occurred during parsing or transformation.
1022///
1023/// @deprecated Only works for Vite.
1024#[allow(clippy::needless_pass_by_value, clippy::allow_attributes)]
1025#[napi]
1026pub fn module_runner_transform(
1027    filename: String,
1028    source_text: String,
1029    options: Option<ModuleRunnerTransformOptions>,
1030) -> ModuleRunnerTransformResult {
1031    let file_path = Path::new(&filename);
1032    let source_type = SourceType::from_path(file_path);
1033    let source_type = match source_type {
1034        Ok(s) => s,
1035        Err(err) => {
1036            return ModuleRunnerTransformResult {
1037                code: String::default(),
1038                map: None,
1039                deps: vec![],
1040                dynamic_deps: vec![],
1041                errors: vec![OxcError::new(err.to_string())],
1042            };
1043        }
1044    };
1045
1046    let allocator = Allocator::default();
1047    let mut parser_ret = Parser::new(&allocator, &source_text, source_type).parse();
1048    let mut program = parser_ret.program;
1049
1050    let SemanticBuilderReturn { semantic, errors } =
1051        SemanticBuilder::new().with_check_syntax_error(true).build(&program);
1052    parser_ret.errors.extend(errors);
1053
1054    let scoping = semantic.into_scoping();
1055    let (deps, dynamic_deps) =
1056        ModuleRunnerTransform::default().transform(&allocator, &mut program, scoping);
1057
1058    let CodegenReturn { code, map, .. } = Codegen::new()
1059        .with_options(CodegenOptions {
1060            source_map_path: options.and_then(|opts| {
1061                opts.sourcemap.as_ref().and_then(|s| s.then(|| file_path.to_path_buf()))
1062            }),
1063            ..Default::default()
1064        })
1065        .build(&program);
1066
1067    ModuleRunnerTransformResult {
1068        code,
1069        map: map.map(Into::into),
1070        deps: deps.into_iter().collect::<Vec<String>>(),
1071        dynamic_deps: dynamic_deps.into_iter().collect::<Vec<String>>(),
1072        errors: OxcError::from_diagnostics(&filename, &source_text, parser_ret.errors),
1073    }
1074}