oxc_transformer/typescript/
options.rs

1use std::{borrow::Cow, fmt};
2
3use serde::{
4    Deserialize, Deserializer,
5    de::{self, Visitor},
6};
7
8fn default_for_jsx_pragma() -> Cow<'static, str> {
9    Cow::Borrowed("React.createElement")
10}
11
12fn default_for_jsx_pragma_frag() -> Cow<'static, str> {
13    Cow::Borrowed("React.Fragment")
14}
15
16fn default_as_true() -> bool {
17    true
18}
19
20#[derive(Debug, Clone, Deserialize)]
21#[serde(default, rename_all = "camelCase", deny_unknown_fields)]
22pub struct TypeScriptOptions {
23    /// Replace the function used when compiling JSX expressions.
24    /// This is so that we know that the import is not a type import, and should not be removed.
25    /// defaults to React
26    #[serde(default = "default_for_jsx_pragma")]
27    pub jsx_pragma: Cow<'static, str>,
28
29    /// Replace the function used when compiling JSX fragment expressions.
30    /// This is so that we know that the import is not a type import, and should not be removed.
31    /// defaults to React.Fragment
32    #[serde(default = "default_for_jsx_pragma_frag")]
33    pub jsx_pragma_frag: Cow<'static, str>,
34
35    /// When set to true, the transform will only remove type-only imports (introduced in TypeScript 3.8).
36    /// This should only be used if you are using TypeScript >= 3.8.
37    pub only_remove_type_imports: bool,
38
39    // Enables compilation of TypeScript namespaces.
40    #[serde(default = "default_as_true")]
41    pub allow_namespaces: bool,
42
43    /// When enabled, type-only class fields are only removed if they are prefixed with the declare modifier:
44    ///
45    /// ## Deprecated
46    ///
47    /// Allowing `declare` fields is built-in support in Oxc without any option. If you want to remove class fields
48    /// without initializer, you can use `remove_class_fields_without_initializer: true` instead.
49    #[serde(default = "default_as_true")]
50    pub allow_declare_fields: bool,
51
52    /// When enabled, class fields without initializers are removed.
53    ///
54    /// For example:
55    /// ```ts
56    /// class Foo {
57    ///    x: number;
58    ///    y: number = 0;
59    /// }
60    /// ```
61    /// // transform into
62    /// ```js
63    /// class Foo {
64    ///    x: number;
65    /// }
66    /// ```
67    ///
68    /// The option is used to align with the behavior of TypeScript's `useDefineForClassFields: false` option.
69    /// When you want to enable this, you also need to set [`crate::CompilerAssumptions::set_public_class_fields`]
70    /// to `true`. The `set_public_class_fields: true` + `remove_class_fields_without_initializer: true` is
71    /// equivalent to `useDefineForClassFields: false` in TypeScript.
72    ///
73    /// When `set_public_class_fields` is true and class-properties plugin is enabled, the above example transforms into:
74    ///
75    /// ```js
76    /// class Foo {
77    ///   constructor() {
78    ///     this.y = 0;
79    ///   }
80    /// }
81    /// ```
82    ///
83    /// Defaults to `false`.
84    #[serde(default)]
85    pub remove_class_fields_without_initializer: bool,
86
87    /// Unused.
88    pub optimize_const_enums: bool,
89
90    // Preset options
91    /// Modifies extensions in import and export declarations.
92    ///
93    /// This option, when used together with TypeScript's [`allowImportingTsExtension`](https://www.typescriptlang.org/tsconfig#allowImportingTsExtensions) option,
94    /// allows to write complete relative specifiers in import declarations while using the same extension used by the source files.
95    ///
96    /// When set to `true`, same as [`RewriteExtensionsMode::Rewrite`]. Defaults to `false` (do nothing).
97    #[serde(deserialize_with = "deserialize_rewrite_import_extensions")]
98    pub rewrite_import_extensions: Option<RewriteExtensionsMode>,
99}
100
101impl Default for TypeScriptOptions {
102    fn default() -> Self {
103        Self {
104            jsx_pragma: default_for_jsx_pragma(),
105            jsx_pragma_frag: default_for_jsx_pragma_frag(),
106            only_remove_type_imports: false,
107            allow_namespaces: default_as_true(),
108            allow_declare_fields: default_as_true(),
109            remove_class_fields_without_initializer: false,
110            optimize_const_enums: false,
111            rewrite_import_extensions: None,
112        }
113    }
114}
115
116#[derive(Debug, Clone, Copy, Default)]
117pub enum RewriteExtensionsMode {
118    /// Rewrite `.ts`/`.mts`/`.cts` extensions in import/export declarations to `.js`/`.mjs`/`.cjs`.
119    #[default]
120    Rewrite,
121    /// Remove `.ts`/`.mts`/`.cts`/`.tsx` extensions in import/export declarations.
122    Remove,
123}
124
125impl RewriteExtensionsMode {
126    pub fn is_remove(self) -> bool {
127        matches!(self, Self::Remove)
128    }
129}
130
131pub fn deserialize_rewrite_import_extensions<'de, D>(
132    deserializer: D,
133) -> Result<Option<RewriteExtensionsMode>, D::Error>
134where
135    D: Deserializer<'de>,
136{
137    struct RewriteExtensionsModeVisitor;
138
139    impl Visitor<'_> for RewriteExtensionsModeVisitor {
140        type Value = Option<RewriteExtensionsMode>;
141
142        fn expecting(&self, formatter: &mut fmt::Formatter) -> fmt::Result {
143            formatter.write_str("true, false, \"rewrite\", or \"remove\"")
144        }
145
146        fn visit_bool<E>(self, value: bool) -> Result<Self::Value, E>
147        where
148            E: de::Error,
149        {
150            if value { Ok(Some(RewriteExtensionsMode::Rewrite)) } else { Ok(None) }
151        }
152
153        fn visit_str<E>(self, value: &str) -> Result<Self::Value, E>
154        where
155            E: de::Error,
156        {
157            match value {
158                "rewrite" => Ok(Some(RewriteExtensionsMode::Rewrite)),
159                "remove" => Ok(Some(RewriteExtensionsMode::Remove)),
160                _ => Err(E::custom(format!(
161                    "Expected RewriteExtensionsMode is either \"rewrite\" or \"remove\" but found: {value}"
162                ))),
163            }
164        }
165    }
166
167    deserializer.deserialize_any(RewriteExtensionsModeVisitor)
168}