diff --git a/napi/transform/index.d.ts b/napi/transform/index.d.ts index f44419c54f23f..fcb4043594495 100644 --- a/napi/transform/index.d.ts +++ b/napi/transform/index.d.ts @@ -427,6 +427,41 @@ export interface TypeScriptOptions { onlyRemoveTypeImports?: boolean allowNamespaces?: boolean allowDeclareFields?: boolean + /** + * When enabled, class fields without initializers are removed. + * + * For example: + * ```ts + * class Foo { + * x: number; + * y: number = 0; + * } + * ``` + * // transform into + * ```js + * class Foo { + * x: number; + * } + * ``` + * + * The option is used to align with the behavior of TypeScript's `useDefineForClassFields: false` option. + * When you want to enable this, you also need to set [`crate::CompilerAssumptions::set_public_class_fields`] + * to `true`. The `set_public_class_fields: true` + `remove_class_fields_without_initializer: true` is + * equivalent to `useDefineForClassFields: false` in TypeScript. + * + * When `set_public_class_fields` is true and class-properties plugin is enabled, the above example transforms into: + * + * ```js + * class Foo { + * constructor() { + * this.y = 0; + * } + * } + * ``` + * + * Defaults to `false`. + */ + removeClassFieldsWithoutInitializer?: boolean /** * Also generate a `.d.ts` declaration file for TypeScript files. * diff --git a/napi/transform/src/transformer.rs b/napi/transform/src/transformer.rs index 588893f65790e..00984a64bc60d 100644 --- a/napi/transform/src/transformer.rs +++ b/napi/transform/src/transformer.rs @@ -220,6 +220,39 @@ pub struct TypeScriptOptions { pub only_remove_type_imports: Option, pub allow_namespaces: Option, pub allow_declare_fields: Option, + /// When enabled, class fields without initializers are removed. + /// + /// For example: + /// ```ts + /// class Foo { + /// x: number; + /// y: number = 0; + /// } + /// ``` + /// // transform into + /// ```js + /// class Foo { + /// x: number; + /// } + /// ``` + /// + /// The option is used to align with the behavior of TypeScript's `useDefineForClassFields: false` option. + /// When you want to enable this, you also need to set [`crate::CompilerAssumptions::set_public_class_fields`] + /// to `true`. The `set_public_class_fields: true` + `remove_class_fields_without_initializer: true` is + /// equivalent to `useDefineForClassFields: false` in TypeScript. + /// + /// When `set_public_class_fields` is true and class-properties plugin is enabled, the above example transforms into: + /// + /// ```js + /// class Foo { + /// constructor() { + /// this.y = 0; + /// } + /// } + /// ``` + /// + /// Defaults to `false`. + pub remove_class_fields_without_initializer: Option, /// Also generate a `.d.ts` declaration file for TypeScript files. /// /// The source file must be compliant with all @@ -252,8 +285,9 @@ impl From for oxc::transformer::TypeScriptOptions { allow_namespaces: options.allow_namespaces.unwrap_or(ops.allow_namespaces), allow_declare_fields: options.allow_declare_fields.unwrap_or(ops.allow_declare_fields), optimize_const_enums: false, - // TODO: Implement - remove_class_fields_without_initializer: false, + remove_class_fields_without_initializer: options + .remove_class_fields_without_initializer + .unwrap_or(ops.remove_class_fields_without_initializer), rewrite_import_extensions: options.rewrite_import_extensions.and_then(|value| { match value { Either::A(v) => { diff --git a/napi/transform/test/transform.test.ts b/napi/transform/test/transform.test.ts index 0401df18572ce..b2752d055ae27 100644 --- a/napi/transform/test/transform.test.ts +++ b/napi/transform/test/transform.test.ts @@ -340,3 +340,61 @@ describe('worker', () => { expect(code).toBe(0); }); }); + +describe('typescript', () => { + describe('options', () => { + test('removeClassFieldsWithoutInitializer', () => { + const code = ` + class Foo { + a: number; + b: number = 1; + } + `; + const ret = transform('test.ts', code, { + typescript: { + removeClassFieldsWithoutInitializer: true, + }, + }); + expect(ret.code).toMatchInlineSnapshot(` + "class Foo { + b = 1; + } + " + `); + }); + + test('align `useDefineForClassFields: false`', () => { + const code = ` + class Foo { + a: number; + b: number = 1; + @dec + c: number; + } + `; + const ret = transform('test.ts', code, { + assumptions: { + setPublicClassFields: true, + }, + target: 'es2020', + typescript: { + removeClassFieldsWithoutInitializer: true, + }, + decorator: { + legacy: true, + }, + }); + expect(ret.code).toMatchInlineSnapshot(` + "import _decorate from "@oxc-project/runtime/helpers/decorate"; + class Foo { + constructor() { + this.b = 1; + this.c = void 0; + } + } + _decorate([dec], Foo.prototype, "c", void 0); + " + `); + }); + }); +});