Emit defineProperty calls before param prop assignments

Note that I restricted this to --useDefineForClassFields is true.
Nothing changes when it's off. I think this is the correct fix for a
patch release.

However, in principal there's nothing wrong with moving parameter
property initialisation after property declaration initialisation. It
would be Extremely Bad and Wrong to rely on this working:

```ts
class C {
  p = this.q // what is q?
  constructor(public q: number) { }
}
```

But today it does, and probably somebody relies on it without knowing.
This commit is contained in:
Nathan Shively-Sanders
2019-11-07 15:18:35 -08:00
parent 07d80edb3f
commit 5810765259
9 changed files with 186 additions and 7 deletions
+12 -6
View File
@@ -10,9 +10,9 @@ namespace ts {
/**
* Transforms ECMAScript Class Syntax.
* TypeScript parameter property syntax is transformed in the TypeScript transformer.
* For now, this transforms public field declarations using TypeScript class semantics
* (where the declarations get elided and initializers are transformed as assignments in the constructor).
* Eventually, this transform will change to the ECMAScript semantics (with Object.defineProperty).
* For now, this transforms public field declarations using TypeScript class semantics,
* where declarations are elided and initializers are transformed as assignments in the constructor.
* When --useDefineForClassFields is on, this transforms to ECMAScript semantics, with Object.defineProperty.
*/
export function transformClassFields(context: TransformationContext) {
const {
@@ -294,7 +294,8 @@ namespace ts {
}
function transformConstructorBody(node: ClassDeclaration | ClassExpression, constructor: ConstructorDeclaration | undefined, isDerivedClass: boolean) {
const properties = getProperties(node, /*requireInitializer*/ !context.getCompilerOptions().useDefineForClassFields, /*isStatic*/ false);
const useDefineForClassFields = context.getCompilerOptions().useDefineForClassFields;
const properties = getProperties(node, /*requireInitializer*/ !useDefineForClassFields, /*isStatic*/ false);
// Only generate synthetic constructor when there are property initializers to move.
if (!constructor && !some(properties)) {
@@ -325,6 +326,9 @@ namespace ts {
if (constructor) {
indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements, visitor);
}
if (useDefineForClassFields) {
addPropertyStatements(statements, properties, createThis());
}
// Add the property initializers. Transforms this:
//
@@ -336,7 +340,7 @@ namespace ts {
// this.x = 1;
// }
//
if (constructor && constructor.body) {
if (constructor?.body) {
let parameterPropertyDeclarationCount = 0;
for (let i = indexOfFirstStatement; i < constructor.body.statements.length; i++) {
if (isParameterPropertyDeclaration(getOriginalNode(constructor.body.statements[i]), constructor)) {
@@ -351,7 +355,9 @@ namespace ts {
indexOfFirstStatement += parameterPropertyDeclarationCount;
}
}
addPropertyStatements(statements, properties, createThis());
if (!useDefineForClassFields) {
addPropertyStatements(statements, properties, createThis());
}
// Add existing statements, skipping the initial super call.
if (constructor) {
@@ -6,6 +6,7 @@ class A {
["computed"] = 13
;[x] = 14
m() { }
constructor(public readonly y: number) { }
}
@@ -13,7 +14,13 @@ class A {
var _a;
var x = "p";
var A = /** @class */ (function () {
function A() {
function A(y) {
Object.defineProperty(this, "y", {
enumerable: true,
configurable: true,
writable: true,
value: void 0
});
Object.defineProperty(this, "a", {
enumerable: true,
configurable: true,
@@ -38,6 +45,7 @@ var A = /** @class */ (function () {
writable: true,
value: 14
});
this.y = y;
}
Object.defineProperty(A.prototype, "m", {
enumerable: false,
@@ -21,5 +21,8 @@ class A {
m() { }
>m : Symbol(A.m, Decl(definePropertyES5.ts, 5, 13))
constructor(public readonly y: number) { }
>y : Symbol(A.y, Decl(definePropertyES5.ts, 7, 16))
}
@@ -25,5 +25,8 @@ class A {
m() { }
>m : () => void
constructor(public readonly y: number) { }
>y : number
}
@@ -0,0 +1,43 @@
//// [definePropertyESNext.ts]
var x: "p" = "p"
class A {
a = 12
b
["computed"] = 13
;[x] = 14
m() { }
constructor(public readonly y: number) { }
}
class B {
}
class C extends B {
z = 1
constructor(public ka: number) {
super()
}
}
//// [definePropertyESNext.js]
var x = "p";
class A {
y;
a = 12;
b;
["computed"] = 13;
[x] = 14;
m() { }
constructor(y) {
this.y = y;
}
}
class B {
}
class C extends B {
ka;
z = 1;
constructor(ka) {
super();
this.ka = ka;
}
}
@@ -0,0 +1,45 @@
=== tests/cases/conformance/classes/propertyMemberDeclarations/definePropertyESNext.ts ===
var x: "p" = "p"
>x : Symbol(x, Decl(definePropertyESNext.ts, 0, 3))
class A {
>A : Symbol(A, Decl(definePropertyESNext.ts, 0, 16))
a = 12
>a : Symbol(A.a, Decl(definePropertyESNext.ts, 1, 9))
b
>b : Symbol(A.b, Decl(definePropertyESNext.ts, 2, 10))
["computed"] = 13
>["computed"] : Symbol(A["computed"], Decl(definePropertyESNext.ts, 3, 5))
>"computed" : Symbol(A["computed"], Decl(definePropertyESNext.ts, 3, 5))
;[x] = 14
>[x] : Symbol(A[x], Decl(definePropertyESNext.ts, 5, 5))
>x : Symbol(x, Decl(definePropertyESNext.ts, 0, 3))
m() { }
>m : Symbol(A.m, Decl(definePropertyESNext.ts, 5, 13))
constructor(public readonly y: number) { }
>y : Symbol(A.y, Decl(definePropertyESNext.ts, 7, 16))
}
class B {
>B : Symbol(B, Decl(definePropertyESNext.ts, 8, 1))
}
class C extends B {
>C : Symbol(C, Decl(definePropertyESNext.ts, 10, 1))
>B : Symbol(B, Decl(definePropertyESNext.ts, 8, 1))
z = 1
>z : Symbol(C.z, Decl(definePropertyESNext.ts, 11, 19))
constructor(public ka: number) {
>ka : Symbol(C.ka, Decl(definePropertyESNext.ts, 13, 16))
super()
>super : Symbol(B, Decl(definePropertyESNext.ts, 8, 1))
}
}
@@ -0,0 +1,51 @@
=== tests/cases/conformance/classes/propertyMemberDeclarations/definePropertyESNext.ts ===
var x: "p" = "p"
>x : "p"
>"p" : "p"
class A {
>A : A
a = 12
>a : number
>12 : 12
b
>b : any
["computed"] = 13
>["computed"] : number
>"computed" : "computed"
>13 : 13
;[x] = 14
>[x] : number
>x : "p"
>14 : 14
m() { }
>m : () => void
constructor(public readonly y: number) { }
>y : number
}
class B {
>B : B
}
class C extends B {
>C : C
>B : B
z = 1
>z : number
>1 : 1
constructor(public ka: number) {
>ka : number
super()
>super() : void
>super : typeof B
}
}
@@ -7,4 +7,5 @@ class A {
["computed"] = 13
;[x] = 14
m() { }
constructor(public readonly y: number) { }
}
@@ -0,0 +1,19 @@
// @target: esnext
// @useDefineForClassFields: true
var x: "p" = "p"
class A {
a = 12
b
["computed"] = 13
;[x] = 14
m() { }
constructor(public readonly y: number) { }
}
class B {
}
class C extends B {
z = 1
constructor(public ka: number) {
super()
}
}