Skip to content

Emit names for methods #17673

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 4 commits into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
15 changes: 13 additions & 2 deletions src/compiler/factory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2777,9 +2777,17 @@ namespace ts {
: createStrictEquality(createTypeOf(value), createLiteral(tag));
}

export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression {
export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange, storePropertyName?: Push<Identifier | StringLiteral | NumericLiteral>, context?: TransformationContext): MemberExpression {
if (isComputedPropertyName(memberName)) {
return setTextRange(createElementAccess(target, memberName.expression), location);
// Assign the name to an identifier so that later we can set it's `.name` property without reinvoking the expression
if (storePropertyName) {
const id = createTempVariable(context.hoistVariableDeclaration);
storePropertyName.push(id);
return setTextRange(createElementAccess(target, createAssignment(id, memberName.expression)), location);
}
else {
return setTextRange(createElementAccess(target, memberName.expression), location);
}
}
else {
const expression = setTextRange(
Expand All @@ -2789,6 +2797,9 @@ namespace ts {
memberName
);
getOrCreateEmitNode(expression).flags |= EmitFlags.NoNestedSourceMaps;
if (storePropertyName) {
storePropertyName.push(isIdentifier(memberName) ? createLiteral(unescapeLeadingUnderscores(memberName.escapedText)) : memberName);
}
return expression;
}
}
Expand Down
74 changes: 62 additions & 12 deletions src/compiler/transformers/es2015.ts
Original file line number Diff line number Diff line change
Expand Up @@ -862,7 +862,9 @@ namespace ts {
startLexicalEnvironment();
addExtendsHelperIfNeeded(statements, node, extendsClauseElement);
addConstructor(statements, node, extendsClauseElement);
addClassMembers(statements, node);
const memberNames: MemberName[] = [];
addClassMembers(statements, node, memberNames);
addNamesHelperIfNeeded(statements, node, memberNames);

// Create a synthetic text range for the return statement.
const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken);
Expand Down Expand Up @@ -906,6 +908,12 @@ namespace ts {
}
}

function addNamesHelperIfNeeded(statements: Statement[], node: ClassExpression | ClassDeclaration, memberNames: MemberName[]) {
if (memberNames && memberNames.length) {
statements.push(createStatement(createNamesHelper(context, createPropertyAccess(getInternalName(node), "prototype"), memberNames)));
}
}

/**
* Adds the constructor of the class to a class body function.
*
Expand Down Expand Up @@ -1570,24 +1578,26 @@ namespace ts {
return statements;
}

type MemberName = Identifier | StringLiteral | NumericLiteral;

/**
* Adds statements to the class body function for a class to define the members of the
* class.
*
* @param statements The statements for the class body function.
* @param node The ClassExpression or ClassDeclaration node.
*/
function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration): void {
function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration, memberNames: Push<MemberName>): void {
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));
case SyntaxKind.MethodDeclaration: {
statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), <MethodDeclaration>member, node, memberNames));
break;

}
case SyntaxKind.GetAccessor:
case SyntaxKind.SetAccessor:
const accessors = getAllAccessorDeclarations(node.members, <AccessorDeclaration>member);
Expand Down Expand Up @@ -1623,11 +1633,11 @@ namespace ts {
* @param receiver The receiver for the member.
* @param member The MethodDeclaration node.
*/
function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration, container: Node) {
function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration, container: Node, memberNames: Push<MemberName>) {
const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None);
const commentRange = getCommentRange(member);
const sourceMapRange = getSourceMapRange(member);
const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name);
const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name, hasModifier(member, ModifierFlags.Static) ? undefined : memberNames, context);
const memberFunction = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined, container);
setEmitFlags(memberFunction, EmitFlags.NoComments);
setSourceMapRange(memberFunction, sourceMapRange);
Expand Down Expand Up @@ -2643,7 +2653,11 @@ namespace ts {

expressions.push(assignment);

addObjectLiteralMembers(expressions, node, temp, numInitialProperties);
const memberNames: MemberName[] = [];
addObjectLiteralMembers(expressions, node, temp, numInitialProperties, memberNames);
if (memberNames && memberNames.length) {
expressions.push(createNamesHelper(context, temp, memberNames));
}

// We need to clone the temporary identifier so that we can write it on a
// new line
Expand Down Expand Up @@ -3068,7 +3082,7 @@ namespace ts {
* @param numInitialNonComputedProperties The number of initial properties without
* computed property names.
*/
function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, start: number) {
function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, start: number, memberNames: Push<MemberName>) {
const properties = node.properties;
const numProperties = properties.length;
for (let i = start; i < numProperties; i++) {
Expand All @@ -3084,7 +3098,7 @@ namespace ts {
break;

case SyntaxKind.MethodDeclaration:
expressions.push(transformObjectLiteralMethodDeclarationToExpression(<MethodDeclaration>property, receiver, node, node.multiLine));
expressions.push(transformObjectLiteralMethodDeclarationToExpression(<MethodDeclaration>property, receiver, node, node.multiLine, memberNames));
break;

case SyntaxKind.PropertyAssignment:
Expand Down Expand Up @@ -3153,12 +3167,15 @@ namespace ts {
* @param method The MethodDeclaration node.
* @param receiver The receiver for the assignment.
*/
function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, container: Node, startsOnNewLine: boolean) {
function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, container: Node, startsOnNewLine: boolean, memberNames: Push<MemberName>) {
const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None);
const expression = createAssignment(
createMemberAccessForPropertyName(
receiver,
visitNode(method.name, visitor, isPropertyName)
visitNode(method.name, visitor, isPropertyName),
/* location */ undefined,
memberNames,
context
),
transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined, container)
);
Expand Down Expand Up @@ -4099,4 +4116,37 @@ namespace ts {
};
})();`
};

function createNamesHelper(context: TransformationContext, proto: PropertyAccessExpression | Identifier, names: (Identifier | StringLiteral | NumericLiteral)[]) {
context.requestEmitHelper(nameHelper);
return createCall(
getHelperName("__names"),
/* typeArguments */ undefined,
[
proto,
createArrayLiteral(names)
]
);
}

const nameHelper: EmitHelper = {
name: "typescript:name",
scoped: false,
priority: 0,
text: `
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();`
};
}
15 changes: 15 additions & 0 deletions tests/baselines/reference/2dArrays.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,20 @@ class Board {
}

//// [2dArrays.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var Cell = (function () {
function Cell() {
}
Expand All @@ -32,5 +46,6 @@ var Board = (function () {
Board.prototype.allShipsSunk = function () {
return this.ships.every(function (val) { return val.isSunk; });
};
__names(Board.prototype, ["allShipsSunk"]);
return Board;
}());
15 changes: 15 additions & 0 deletions tests/baselines/reference/ClassDeclaration11.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@ class C {
}

//// [ClassDeclaration11.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var C = (function () {
function C() {
}
C.prototype.foo = function () { };
__names(C.prototype, ["foo"]);
return C;
}());
15 changes: 15 additions & 0 deletions tests/baselines/reference/ClassDeclaration13.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@ class C {
}

//// [ClassDeclaration13.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var C = (function () {
function C() {
}
C.prototype.bar = function () { };
__names(C.prototype, ["bar"]);
return C;
}());
15 changes: 15 additions & 0 deletions tests/baselines/reference/ClassDeclaration21.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@ class C {
}

//// [ClassDeclaration21.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var C = (function () {
function C() {
}
C.prototype[1] = function () { };
__names(C.prototype, [1]);
return C;
}());
15 changes: 15 additions & 0 deletions tests/baselines/reference/ClassDeclaration22.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,24 @@ class C {
}

//// [ClassDeclaration22.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var C = (function () {
function C() {
}
C.prototype["bar"] = function () { };
__names(C.prototype, ["bar"]);
return C;
}());
18 changes: 17 additions & 1 deletion tests/baselines/reference/ES5For-ofTypeCheck10.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,20 @@ class StringIterator {
for (var v of new StringIterator) { }

//// [ES5For-ofTypeCheck10.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
// In ES3/5, you cannot for...of over an arbitrary iterable.
var StringIterator = (function () {
function StringIterator() {
Expand All @@ -25,10 +39,12 @@ var StringIterator = (function () {
value: ""
};
};
StringIterator.prototype[Symbol.iterator] = function () {
StringIterator.prototype[_a = Symbol.iterator] = function () {
return this;
};
__names(StringIterator.prototype, ["next", _a]);
return StringIterator;
var _a;
}());
for (var _i = 0, _a = new StringIterator; _i < _a.length; _i++) {
var v = _a[_i];
Expand Down
18 changes: 17 additions & 1 deletion tests/baselines/reference/ES5SymbolProperty2.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,14 +11,30 @@ module M {
(new M.C)[Symbol.iterator];

//// [ES5SymbolProperty2.js]
var __names = (this && this.__names) || (function() {
var name = Object.defineProperty ? (function(proto, name) {
Object.defineProperty(proto[name], 'name', {
value: name, configurable: true
});
}) : (function(proto, name) {
proto[name].name = name;
});
return function (proto, keys) {
for (var i = keys.length - 1; i >= 0; i--) {
name(proto, keys[i])
}
};
})();
var M;
(function (M) {
var Symbol;
var C = (function () {
function C() {
}
C.prototype[Symbol.iterator] = function () { };
C.prototype[_a = Symbol.iterator] = function () { };
__names(C.prototype, [_a]);
return C;
var _a;
}());
M.C = C;
(new C)[Symbol.iterator];
Expand Down
Loading