Skip to content
  •  
  •  
  •  
76 changes: 72 additions & 4 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1575,21 +1575,54 @@ namespace ts {
* @param node The ClassExpression or ClassDeclaration node.
*/
function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration): void {
if (!node.members.length) {
return;
}

const originalPrototypeAccess = factory.createPropertyAccessExpression(factory.getInternalName(node), "prototype");
let prototypeStorageName: Identifier | PropertyAccessExpression;

// If the class has multiple non-static member names, it'll store that prototype as a variable that can be minified:
// var ClassName_prototype = ClassName.prototype;
// ClassName_prototype.memberOne = ...
// ClassName_prototype.memberTwo = ...
if (membersContainMultipleUniqueNames(node.members)) {
prototypeStorageName = factory.createUniqueName(node.name ? `${node.name.escapedText}_prototype` : "proto", GeneratedIdentifierFlags.Optimistic);
statements.push(
factory.createVariableStatement(
/*modifiers*/ undefined,
factory.createVariableDeclarationList([
factory.createVariableDeclaration(
prototypeStorageName,
/*exclamationToken*/ undefined,
/*type*/ undefined,
originalPrototypeAccess
)
])
)
);
}
// Since the class has exactly one non-static member, it'll access that prototype member directly on itself:
// ClassName.prototype.member = ...
else {
prototypeStorageName = originalPrototypeAccess;
}

for (const member of node.members) {
switch (member.kind) {
case SyntaxKind.SemicolonClassElement:
statements.push(transformSemicolonClassElementToStatement(<SemicolonClassElement>member));
break;

case SyntaxKind.MethodDeclaration:
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), <MethodDeclaration>member, node));
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member, prototypeStorageName), <MethodDeclaration>member, node));
break;

case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
const accessors = getAllAccessorDeclarations(node.members, <AccessorDeclaration>member);
if (member === accessors.firstAccessor) {
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors, node));
statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member, prototypeStorageName), accessors, node));
}

break;
Expand All @@ -1605,6 +1638,41 @@ namespace ts {
}
}

function classMemberAssignsToPrototype(node: ClassElement) {
return !(getEffectiveModifierFlags(node) & ModifierFlags.Static) && !isConstructorDeclaration(node);
}

function membersContainMultipleUniqueNames(members: NodeArray<ClassElement>) {
if (members.length <= 1) {
return false;
}

let foundName: string | undefined;

for (const member of members) {
if (!classMemberAssignsToPrototype(member)) {
continue;
}

// If a name isn't immediately identifiable, we assume it's unique
if (!member.name || !isPropertyNameLiteral(member.name)) {
return true;
}

const text = getTextOfIdentifierOrLiteral(member.name);
if (foundName === undefined) {
foundName = text;
continue;
}

if (text !== foundName) {
return true;
}
}

return false;
}

/**
* Transforms a SemicolonClassElement into a statement for a class body function.
*
Expand Down Expand Up @@ -4313,10 +4381,10 @@ namespace ts {
return node;
}

function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) {
function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement, prototypeStorageName: LeftHandSideExpression) {
return hasSyntacticModifier(member, ModifierFlags.Static)
? factory.getInternalName(node)
: factory.createPropertyAccessExpression(factory.getInternalName(node), "prototype");
: prototypeStorageName;
}

function hasSynthesizedDefaultSuperCall(constructor: ConstructorDeclaration | undefined, hasExtendsClause: boolean) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/ES5For-ofTypeCheck10.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,14 @@ for (var v of new StringIterator) { }
var StringIterator = /** @class */ (function () {
function StringIterator() {
}
StringIterator.prototype.next = function () {
var StringIterator_prototype = StringIterator.prototype;
StringIterator_prototype.next = function () {
return {
done: true,
value: ""
};
};
StringIterator.prototype[Symbol.iterator] = function () {
StringIterator_prototype[Symbol.iterator] = function () {
return this;
};
return StringIterator;
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/abstractProperty.js
Original file line number Diff line number Diff line change
Expand Up @@ -50,12 +50,13 @@ var C = /** @class */ (function (_super) {
_this.ro = "readonly please";
return _this;
}
Object.defineProperty(C.prototype, "prop", {
var C_prototype = C.prototype;
Object.defineProperty(C_prototype, "prop", {
get: function () { return "foo"; },
set: function (v) { },
enumerable: false,
configurable: true
});
C.prototype.m = function () { };
C_prototype.m = function () { };
return C;
}(B));
5 changes: 3 additions & 2 deletions tests/baselines/reference/abstractPropertyNegative.js
Original file line number Diff line number Diff line change
Expand Up @@ -123,13 +123,14 @@ var WrongTypeAccessorImpl2 = /** @class */ (function (_super) {
var AbstractAccessorMismatch = /** @class */ (function () {
function AbstractAccessorMismatch() {
}
Object.defineProperty(AbstractAccessorMismatch.prototype, "p1", {
var AbstractAccessorMismatch_prototype = AbstractAccessorMismatch.prototype;
Object.defineProperty(AbstractAccessorMismatch_prototype, "p1", {
set: function (val) { },
enumerable: false,
configurable: true
});
;
Object.defineProperty(AbstractAccessorMismatch.prototype, "p2", {
Object.defineProperty(AbstractAccessorMismatch_prototype, "p2", {
get: function () { return "should work"; },
enumerable: false,
configurable: true
Expand Down
7 changes: 4 additions & 3 deletions tests/baselines/reference/accessibilityModifiers.js
Original file line number Diff line number Diff line change
Expand Up @@ -127,13 +127,14 @@ var D = /** @class */ (function () {
var E = /** @class */ (function () {
function E() {
}
E.prototype.method = function () { };
Object.defineProperty(E.prototype, "getter", {
var E_prototype = E.prototype;
E_prototype.method = function () { };
Object.defineProperty(E_prototype, "getter", {
get: function () { return 0; },
enumerable: false,
configurable: true
});
Object.defineProperty(E.prototype, "setter", {
Object.defineProperty(E_prototype, "setter", {
set: function (a) { },
enumerable: false,
configurable: true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,25 +17,26 @@ class LanguageSpec_section_4_5_error_cases {
var LanguageSpec_section_4_5_error_cases = /** @class */ (function () {
function LanguageSpec_section_4_5_error_cases() {
}
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterFirst", {
var LanguageSpec_section_4_5_error_cases_prototype = LanguageSpec_section_4_5_error_cases.prototype;
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedSetter_SetterFirst", {
get: function () { return ""; },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedSetter_SetterLast", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedSetter_SetterLast", {
get: function () { return ""; },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedGetter_GetterFirst", {
get: function () { return ""; },
set: function (aStr) { aStr = 0; },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_error_cases.prototype, "AnnotatedGetter_GetterLast", {
Object.defineProperty(LanguageSpec_section_4_5_error_cases_prototype, "AnnotatedGetter_GetterLast", {
get: function () { return ""; },
set: function (aStr) { aStr = 0; },
enumerable: false,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,37 +55,38 @@ var B = /** @class */ (function (_super) {
var LanguageSpec_section_4_5_inference = /** @class */ (function () {
function LanguageSpec_section_4_5_inference() {
}
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation", {
var LanguageSpec_section_4_5_inference_prototype = LanguageSpec_section_4_5_inference.prototype;
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredGetterFromSetterAnnotation", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredGetterFromSetterAnnotation_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredGetterFromSetterAnnotation_GetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredFromGetter", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredFromGetter_SetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredFromGetter_SetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredSetterFromGetterAnnotation", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
configurable: true
});
Object.defineProperty(LanguageSpec_section_4_5_inference.prototype, "InferredSetterFromGetterAnnotation_GetterFirst", {
Object.defineProperty(LanguageSpec_section_4_5_inference_prototype, "InferredSetterFromGetterAnnotation_GetterFirst", {
get: function () { return new B(); },
set: function (a) { },
enumerable: false,
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/ambiguousCallsWhereReturnTypesAgree.js
Original file line number Diff line number Diff line change
Expand Up @@ -32,20 +32,22 @@ class TestClass2 {
var TestClass = /** @class */ (function () {
function TestClass() {
}
TestClass.prototype.bar = function (x) {
var TestClass_prototype = TestClass.prototype;
TestClass_prototype.bar = function (x) {
};
TestClass.prototype.foo = function (x) {
TestClass_prototype.foo = function (x) {
this.bar(x); // should not error
};
return TestClass;
}());
var TestClass2 = /** @class */ (function () {
function TestClass2() {
}
TestClass2.prototype.bar = function (x) {
var TestClass2_prototype = TestClass2.prototype;
TestClass2_prototype.bar = function (x) {
return 0;
};
TestClass2.prototype.foo = function (x) {
TestClass2_prototype.foo = function (x) {
return this.bar(x); // should not error
};
return TestClass2;
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest1.js
Original file line number Diff line number Diff line change
Expand Up @@ -104,8 +104,9 @@ var __extends = (this && this.__extends) || (function () {
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.IM1 = function () { return null; };
C1.prototype.C1M1 = function () { return null; };
var C1_prototype = C1.prototype;
C1_prototype.IM1 = function () { return null; };
C1_prototype.C1M1 = function () { return null; };
return C1;
}());
var C2 = /** @class */ (function (_super) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest2.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,9 @@ var __extends = (this && this.__extends) || (function () {
var C1 = /** @class */ (function () {
function C1() {
}
C1.prototype.IM1 = function () { return null; };
C1.prototype.C1M1 = function () { return null; };
var C1_prototype = C1.prototype;
C1_prototype.IM1 = function () { return null; };
C1_prototype.C1M1 = function () { return null; };
return C1;
}());
var C2 = /** @class */ (function (_super) {
Expand Down
5 changes: 3 additions & 2 deletions tests/baselines/reference/arrayAssignmentTest5.js
Original file line number Diff line number Diff line change
Expand Up @@ -39,14 +39,15 @@ var Test;
var Bug = /** @class */ (function () {
function Bug() {
}
Bug.prototype.onEnter = function (line, state, offset) {
var Bug_prototype = Bug.prototype;
Bug_prototype.onEnter = function (line, state, offset) {
var lineTokens = this.tokenize(line, state, true);
var tokens = lineTokens.tokens;
if (tokens.length === 0) {
return this.onEnter(line, tokens, offset); // <== this should produce an error since onEnter can not be called with (string, IStateToken[], offset)
}
};
Bug.prototype.tokenize = function (line, state, includeStates) {
Bug_prototype.tokenize = function (line, state, includeStates) {
return null;
};
return Bug;
Expand Down
10 changes: 6 additions & 4 deletions tests/baselines/reference/arrayBestCommonTypes.js
Original file line number Diff line number Diff line change
Expand Up @@ -145,11 +145,12 @@ var EmptyTypes;
var f = /** @class */ (function () {
function f() {
}
f.prototype.voidIfAny = function (x, y) {
var f_prototype = f.prototype;
f_prototype.voidIfAny = function (x, y) {
if (y === void 0) { y = false; }
return null;
};
f.prototype.x = function () {
f_prototype.x = function () {
(this.voidIfAny([4, 2][0]));
(this.voidIfAny([4, 2, undefined][0]));
(this.voidIfAny([undefined, 2, 4][0]));
Expand Down Expand Up @@ -204,11 +205,12 @@ var NonEmptyTypes;
var f = /** @class */ (function () {
function f() {
}
f.prototype.voidIfAny = function (x, y) {
var f_prototype_1 = f.prototype;
f_prototype_1.voidIfAny = function (x, y) {
if (y === void 0) { y = false; }
return null;
};
f.prototype.x = function () {
f_prototype_1.x = function () {
(this.voidIfAny([4, 2][0]));
(this.voidIfAny([4, 2, undefined][0]));
(this.voidIfAny([undefined, 2, 4][0]));
Expand Down
Loading