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}