Skip to main content

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