oxc
oxc copied to clipboard
transformer: class constructor argument access modifier special behavior
In TypeScript, the following class (case 1) reports a type error:
class Foo {
x = this.foo // error: Property 'foo' is used before its initialization
foo: any
constructor(foo: any) {
this.foo = foo
}
}
Because it will be transformed to:
class Foo {
constructor(foo) {
this.x = this.foo; // <-- this.foo not assigned yet!
this.foo = foo;
}
}
However, if it uses access modifier on the constructor argument (case 2):
class Foo {
x = this.foo
constructor(public foo: any) {
console.log(this.foo)
}
}
It will work both in types and at runtime, because it will be transformed to:
class Foo {
constructor(foo) {
this.foo = foo; // <-- injected from argument
this.x = this.foo; // <-- moved from field initializers
console.log(this.foo);
}
}
Notice how x = this.foo
is moved into the constructor to be after the this.foo
assignment.
Oxc's current behavior
Oxc currently transforms case 2 to:
class Foo {
x = this.foo
constructor(foo) {
console.log(this.foo)
this.foo = foo // <-- injected from argument
}
}
Notice that both x = this.foo
and console.log(this.foo)
are executed before the this.foo
assignment, both leading to runtime errors.
What needs to be fixed
- The generated assignment for constructor arguments with access modifiers should be injected to the top of the constructor, not the bottom.
- When constructor argument access modifiers are used, all class field initializers need to be moved into the constructor (so their initialization order is preserved), after the argument assignments, and before existing constructor code.
Related: Behavior with useDefineForClassFields: true
When useDefineForClassFields
is set to true, case 2 will also throw a type error. And TS's transform out will become:
class Foo {
foo;
x = this.foo;
constructor(foo) {
this.foo = foo;
console.log(this.foo);
}
}
I'm not sure if oxc's TS transform currently takes this into account, but this is something we will need to consider.