Skip to content

Commit b29c9cf

Browse files
authored
Merge pull request #10 from joeywatts/class-properties-esnext
Moves the transformation of class properties to the ESNext transformer. Property initializers are moved to the constructor by the TypeScript transformer only if there is constructor parameter properties. This is to preserve property initialization order. It currently does not produce void 0 initializers for property declarations that have no initializer due to ESNext and TypeScript having different runtime behaviors. TS completely elides property declarations with no initializer, while I believe the ESNext would define that property on the instance with an undefined value (which is observable at runtime - propertyName in instance or for (var prop in instance) {}).
2 parents 221111e + 861936e commit b29c9cf

23 files changed

+836
-523
lines changed

src/compiler/binder.ts

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3189,8 +3189,7 @@ namespace ts {
31893189
// A ClassDeclaration is ES6 syntax.
31903190
transformFlags = subtreeFlags | TransformFlags.AssertES2015;
31913191

3192-
// A class with a parameter property assignment, property initializer, computed property name, or decorator is
3193-
// TypeScript syntax.
3192+
// A class with a parameter property assignment or decorator is TypeScript syntax.
31943193
// An exported declaration may be TypeScript syntax, but is handled by the visitor
31953194
// for a namespace declaration.
31963195
if ((subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax)
@@ -3213,8 +3212,7 @@ namespace ts {
32133212
// A ClassExpression is ES6 syntax.
32143213
let transformFlags = subtreeFlags | TransformFlags.AssertES2015;
32153214

3216-
// A class with a parameter property assignment, property initializer, or decorator is
3217-
// TypeScript syntax.
3215+
// A class with a parameter property assignment or decorator is TypeScript syntax.
32183216
if (subtreeFlags & TransformFlags.ContainsTypeScriptClassSyntax
32193217
|| node.typeParameters) {
32203218
transformFlags |= TransformFlags.AssertTypeScript;
@@ -3310,7 +3308,6 @@ namespace ts {
33103308
|| hasModifier(node, ModifierFlags.TypeScriptModifier)
33113309
|| node.typeParameters
33123310
|| node.type
3313-
|| (node.name && isComputedPropertyName(node.name)) // While computed method names aren't typescript, the TS transform must visit them to emit property declarations correctly
33143311
|| !node.body) {
33153312
transformFlags |= TransformFlags.AssertTypeScript;
33163313
}
@@ -3341,7 +3338,6 @@ namespace ts {
33413338
if (node.decorators
33423339
|| hasModifier(node, ModifierFlags.TypeScriptModifier)
33433340
|| node.type
3344-
|| (node.name && isComputedPropertyName(node.name)) // While computed accessor names aren't typescript, the TS transform must visit them to emit property declarations correctly
33453341
|| !node.body) {
33463342
transformFlags |= TransformFlags.AssertTypeScript;
33473343
}
@@ -3356,12 +3352,16 @@ namespace ts {
33563352
}
33573353

33583354
function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) {
3359-
// A PropertyDeclaration is TypeScript syntax.
3360-
let transformFlags = subtreeFlags | TransformFlags.AssertTypeScript;
3355+
// A PropertyDeclaration is ESnext syntax.
3356+
let transformFlags = subtreeFlags | TransformFlags.AssertESNext;
33613357

3362-
// If the PropertyDeclaration has an initializer or a computed name, we need to inform its ancestor
3363-
// so that it handle the transformation.
3364-
if (node.initializer || isComputedPropertyName(node.name)) {
3358+
// Decorators, TypeScript-specific modifiers, and type annotations are TypeScript syntax.
3359+
if (some(node.decorators) || hasModifier(node, ModifierFlags.TypeScriptModifier) || node.type) {
3360+
transformFlags |= TransformFlags.AssertTypeScript;
3361+
}
3362+
3363+
// Hoisted variables related to class properties should live within the TypeScript class wrapper.
3364+
if (isComputedPropertyName(node.name) || (hasStaticModifier(node) && node.initializer)) {
33653365
transformFlags |= TransformFlags.ContainsTypeScriptClassSyntax;
33663366
}
33673367

@@ -3762,6 +3762,8 @@ namespace ts {
37623762
break;
37633763

37643764
case SyntaxKind.ComputedPropertyName:
3765+
// Computed property names are transformed by the ESNext transformer.
3766+
transformFlags |= TransformFlags.AssertESNext;
37653767
// Even though computed property names are ES6, we don't treat them as such.
37663768
// This is so that they can flow through PropertyName transforms unaffected.
37673769
// Instead, we mark the container as ES6, so that it can properly handle the transform.

src/compiler/transformers/esnext.ts

Lines changed: 438 additions & 4 deletions
Large diffs are not rendered by default.

src/compiler/transformers/ts.ts

Lines changed: 146 additions & 347 deletions
Large diffs are not rendered by default.

src/compiler/transformers/utilities.ts

Lines changed: 80 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -240,6 +240,47 @@ namespace ts {
240240
isIdentifier(expression);
241241
}
242242

243+
/**
244+
* A simple inlinable expression is an expression which can be copied into multiple locations
245+
* without risk of repeating any sideeffects and whose value could not possibly change between
246+
* any such locations
247+
*/
248+
export function isSimpleInlineableExpression(expression: Expression) {
249+
return !isIdentifier(expression) && isSimpleCopiableExpression(expression) ||
250+
isWellKnownSymbolSyntactically(expression);
251+
}
252+
253+
/**
254+
* Adds super call and preceding prologue directives into the list of statements.
255+
*
256+
* @param ctor The constructor node.
257+
* @param result The list of statements.
258+
* @param visitor The visitor to apply to each node added to the result array.
259+
* @returns index of the statement that follows super call
260+
*/
261+
export function addPrologueDirectivesAndInitialSuperCall(ctor: ConstructorDeclaration, result: Statement[], visitor: Visitor): number {
262+
if (ctor.body) {
263+
const statements = ctor.body.statements;
264+
// add prologue directives to the list (if any)
265+
const index = addPrologue(result, statements, /*ensureUseStrict*/ false, visitor);
266+
if (index === statements.length) {
267+
// list contains nothing but prologue directives (or empty) - exit
268+
return index;
269+
}
270+
271+
const statement = statements[index];
272+
if (statement.kind === SyntaxKind.ExpressionStatement && isSuperCall((<ExpressionStatement>statement).expression)) {
273+
result.push(visitNode(statement, visitor, isStatement));
274+
return index + 1;
275+
}
276+
277+
return index;
278+
}
279+
280+
return 0;
281+
}
282+
283+
243284
/**
244285
* @param input Template string input strings
245286
* @param args Names which need to be made file-level unique
@@ -255,4 +296,43 @@ namespace ts {
255296
return result;
256297
};
257298
}
299+
300+
/**
301+
* Gets all property declarations with initializers on either the static or instance side of a class.
302+
*
303+
* @param node The class node.
304+
* @param isStatic A value indicating whether to get properties from the static or instance side of the class.
305+
*/
306+
export function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): ReadonlyArray<PropertyDeclaration> {
307+
return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty);
308+
}
309+
310+
/**
311+
* Gets a value indicating whether a class element is a static property declaration with an initializer.
312+
*
313+
* @param member The class element node.
314+
*/
315+
export function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
316+
return isInitializedProperty(member) && hasStaticModifier(member);
317+
}
318+
319+
/**
320+
* Gets a value indicating whether a class element is an instance property declaration with an initializer.
321+
*
322+
* @param member The class element node.
323+
*/
324+
export function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
325+
return isInitializedProperty(member) && !hasStaticModifier(member);
326+
}
327+
328+
/**
329+
* Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer.
330+
*
331+
* @param member The class element node.
332+
* @param isStatic A value indicating whether the member should be a static or instance member.
333+
*/
334+
export function isInitializedProperty(member: ClassElement): member is PropertyDeclaration & { initializer: Expression; } {
335+
return member.kind === SyntaxKind.PropertyDeclaration
336+
&& (<PropertyDeclaration>member).initializer !== undefined;
337+
}
258338
}

tests/baselines/reference/declarationEmitPrivateSymbolCausesVarDeclarationEmit2.js

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@ var C = /** @class */ (function () {
3434
}
3535
return C;
3636
}());
37-
_a = a_1.x;
3837
exports.C = C;
38+
_a = a_1.x;
3939
//// [c.js]
4040
"use strict";
4141
var __extends = (this && this.__extends) || (function () {
@@ -64,8 +64,8 @@ var D = /** @class */ (function (_super) {
6464
}
6565
return D;
6666
}(b_1.C));
67-
_a = a_1.x;
6867
exports.D = D;
68+
_a = a_1.x;
6969

7070

7171
//// [a.d.ts]

0 commit comments

Comments
 (0)