Skip to content

Commit 6bff64e

Browse files
committed
feat(transformer/typescript): support removeClassFieldsWithoutInitializer option (#10576)
* close #9192 * close #10491 We've discussed adding `removeClassFieldsWithoutInitializer` option to support removing class fields without an initializer in #10491 (comment). This is used to align the`TypeScript`'s `useDefineForClassFields: false` option.
1 parent 39adefe commit 6bff64e

File tree

13 files changed

+108
-8
lines changed

13 files changed

+108
-8
lines changed

crates/oxc_transformer/src/typescript/annotations.rs

+8-5
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ pub struct TypeScriptAnnotations<'a, 'ctx> {
2727
has_jsx_fragment: bool,
2828
jsx_element_import_name: String,
2929
jsx_fragment_import_name: String,
30+
remove_class_fields_without_initializer: bool,
3031
}
3132

3233
impl<'a, 'ctx> TypeScriptAnnotations<'a, 'ctx> {
@@ -52,6 +53,8 @@ impl<'a, 'ctx> TypeScriptAnnotations<'a, 'ctx> {
5253
has_jsx_fragment: false,
5354
jsx_element_import_name,
5455
jsx_fragment_import_name,
56+
remove_class_fields_without_initializer: options
57+
.remove_class_fields_without_initializer,
5558
}
5659
}
5760
}
@@ -227,11 +230,11 @@ impl<'a> Traverse<'a> for TypeScriptAnnotations<'a, '_> {
227230
&& !method.value.is_typescript_syntax()
228231
}
229232
ClassElement::PropertyDefinition(prop) => {
230-
if prop.declare {
231-
false
232-
} else {
233-
matches!(prop.r#type, PropertyDefinitionType::PropertyDefinition)
234-
}
233+
matches!(prop.r#type, PropertyDefinitionType::PropertyDefinition)
234+
&& !prop.declare
235+
&& !(self.remove_class_fields_without_initializer
236+
&& prop.value.is_none()
237+
&& prop.decorators.is_empty())
235238
}
236239
ClassElement::AccessorProperty(prop) => {
237240
matches!(prop.r#type, AccessorPropertyType::AccessorProperty)

crates/oxc_transformer/src/typescript/options.rs

+36
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,41 @@ pub struct TypeScriptOptions {
4444
#[serde(default = "default_as_true")]
4545
pub allow_declare_fields: bool,
4646

47+
/// When enabled, class fields without initializers are removed.
48+
///
49+
/// For example:
50+
/// ```ts
51+
/// class Foo {
52+
/// x: number;
53+
/// y: number = 0;
54+
/// }
55+
/// ```
56+
/// // transform into
57+
/// ```js
58+
/// class Foo {
59+
/// x: number;
60+
/// }
61+
/// ```
62+
///
63+
/// The option is used to align with the behavior of TypeScript's `useDefineForClassFields: false` option.
64+
/// When you want to enable this, you also need to set [`crate::CompilerAssumptions::set_public_class_fields`]
65+
/// to `true`. The `set_public_class_fields: true` + `remove_class_fields_without_initializer: true` is
66+
/// equivalent to `useDefineForClassFields: false` in TypeScript.
67+
///
68+
/// When `set_public_class_fields` is true and class-properties plugin is enabled, the above example transforms into:
69+
///
70+
/// ```js
71+
/// class Foo {
72+
/// constructor() {
73+
/// this.y = 0;
74+
/// }
75+
/// }
76+
/// ```
77+
///
78+
/// Defaults to `false`.
79+
#[serde(default)]
80+
pub remove_class_fields_without_initializer: bool,
81+
4782
/// Unused.
4883
pub optimize_const_enums: bool,
4984

@@ -66,6 +101,7 @@ impl Default for TypeScriptOptions {
66101
only_remove_type_imports: false,
67102
allow_namespaces: default_as_true(),
68103
allow_declare_fields: default_as_true(),
104+
remove_class_fields_without_initializer: false,
69105
optimize_const_enums: false,
70106
rewrite_import_extensions: None,
71107
}

napi/transform/src/transformer.rs

+2
Original file line numberDiff line numberDiff line change
@@ -252,6 +252,8 @@ impl From<TypeScriptOptions> for oxc::transformer::TypeScriptOptions {
252252
allow_namespaces: options.allow_namespaces.unwrap_or(ops.allow_namespaces),
253253
allow_declare_fields: options.allow_declare_fields.unwrap_or(ops.allow_declare_fields),
254254
optimize_const_enums: false,
255+
// TODO: Implement
256+
remove_class_fields_without_initializer: false,
255257
rewrite_import_extensions: options.rewrite_import_extensions.and_then(|value| {
256258
match value {
257259
Either::A(v) => {

tasks/transform_conformance/snapshots/oxc.snap.md

+3-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
commit: 578ac4df
22

3-
Passed: 150/246
3+
Passed: 153/249
44

55
# All Passed:
66
* babel-plugin-transform-class-static-block
@@ -44,7 +44,7 @@ after transform: SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), R
4444
rebuilt : SymbolId(0): [ReferenceId(0), ReferenceId(2), ReferenceId(6), ReferenceId(10)]
4545

4646

47-
# babel-plugin-transform-typescript (4/20)
47+
# babel-plugin-transform-typescript (6/22)
4848
* class-property-definition/input.ts
4949
Unresolved references mismatch:
5050
after transform: ["const"]
@@ -424,7 +424,7 @@ after transform: SymbolId(4): ScopeId(1)
424424
rebuilt : SymbolId(5): ScopeId(4)
425425

426426

427-
# legacy-decorators (2/71)
427+
# legacy-decorators (3/72)
428428
* oxc/metadata/bound-type-reference/input.ts
429429
Symbol reference IDs mismatch for "BoundTypeReference":
430430
after transform: SymbolId(0): [ReferenceId(1), ReferenceId(3), ReferenceId(4), ReferenceId(5), ReferenceId(6)]
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Cls {
2+
x: number;
3+
y = 1;
4+
@dce
5+
z: string;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"plugins": [["transform-typescript", { "removeClassFieldsWithoutInitializer": true }]]
3+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Cls {
2+
y = 1;
3+
@dce
4+
z;
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
class Cls {
2+
x: number;
3+
y = 1;
4+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
{
2+
"assumptions": {
3+
"setPublicClassFields": true
4+
},
5+
"plugins": [
6+
"transform-class-properties",
7+
[
8+
"transform-typescript",
9+
{
10+
"removeClassFieldsWithoutInitializer": true
11+
}
12+
]
13+
]
14+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Cls {
2+
constructor() {
3+
this.y = 1;
4+
}
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
class Cls {
2+
x: number;
3+
y = 1;
4+
@dce
5+
z: string;
6+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
{
2+
"plugins": [
3+
"transform-legacy-decorator",
4+
[
5+
"transform-typescript",
6+
{
7+
"removeClassFieldsWithoutInitializer": true
8+
}
9+
]
10+
]
11+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
class Cls {
2+
y = 1;
3+
z;
4+
}
5+
babelHelpers.decorate([dce], Cls.prototype, "z", void 0);

0 commit comments

Comments
 (0)