diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index a5565a10632..51f97727ca4 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -11267,6 +11267,29 @@ module ts { !hasProperty(getGeneratedNamesForSourceFile(getSourceFile(location)), name); } + function getClassDeclarationVariableId(n: Identifier): number { + Debug.assert(!nodeIsSynthesized(n)); + + let isClassDeclaration = n.parent.kind === SyntaxKind.ClassDeclaration; + + let symbol = + (isClassDeclaration ? getSymbolOfNode(n.parent) : undefined) || + getNodeLinks(n).resolvedSymbol || + resolveName(n, n.text, SymbolFlags.Value | SymbolFlags.Alias, /*nodeNotFoundMessage*/ undefined, /*nameArg*/ undefined); + + symbol = getExportSymbolOfValueSymbolIfExported(symbol); + + let isClass = symbol && (symbol.flags & SymbolFlags.Class) !== 0; + + if (isClass) { + // side-effect of calling this method: + // assign id to symbol if it was not yet set + getSymbolLinks(symbol); + return symbol.id; + } + return undefined; + } + function getBlockScopedVariableId(n: Identifier): number { Debug.assert(!nodeIsSynthesized(n)); @@ -11309,6 +11332,7 @@ module ts { getConstantValue, isUnknownIdentifier, getBlockScopedVariableId, + getClassDeclarationVariableId, }; } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 205f2fe2945..e6354a96128 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -277,20 +277,14 @@ module ts { } } - function getFirstConstructorWithBody(node: ClassDeclaration): ConstructorDeclaration { - return forEach(node.members, member => { - if (member.kind === SyntaxKind.Constructor && nodeIsPresent((member).body)) { - return member; - } - }); - } - function getAllAccessorDeclarations(declarations: NodeArray, accessor: AccessorDeclaration) { let firstAccessor: AccessorDeclaration; + let lastAccessor: AccessorDeclaration; let getAccessor: AccessorDeclaration; let setAccessor: AccessorDeclaration; if (hasDynamicName(accessor)) { firstAccessor = accessor; + lastAccessor = accessor; if (accessor.kind === SyntaxKind.GetAccessor) { getAccessor = accessor; } @@ -312,6 +306,7 @@ module ts { firstAccessor = member; } + lastAccessor = member; if (member.kind === SyntaxKind.GetAccessor && !getAccessor) { getAccessor = member; } @@ -325,6 +320,7 @@ module ts { } return { firstAccessor, + lastAccessor, getAccessor, setAccessor }; @@ -1582,8 +1578,12 @@ module ts { let currentScopeNames: Map; let generatedBlockScopeNames: string[]; + let generatedClassNames: string[]; + let generatedComputedPropertyNames: string[]; + let generatedSetterNames: string[]; let extendsEmitted = false; + let decorateEmitted = false; let tempCount = 0; let tempVariables: Identifier[]; let tempParameters: Identifier[]; @@ -2565,6 +2565,25 @@ module ts { emitLiteral(node); } else if (node.kind === SyntaxKind.ComputedPropertyName) { + if (nodeIsDecorated(node.parent)) { + if (!generatedComputedPropertyNames) { + generatedComputedPropertyNames = []; + } + + let generatedName = generatedComputedPropertyNames[node.id]; + if (generatedName) { + write(generatedName); + return; + } + + let generatedVariable = createTempVariable(node); + generatedName = generatedVariable.text; + recordTempDeclaration(generatedVariable); + generatedComputedPropertyNames[node.id] = generatedName; + write(generatedName); + write(" = "); + } + emit((node).expression); } else { @@ -2623,26 +2642,29 @@ module ts { } } - function getGeneratedNameForIdentifier(node: Identifier): string { - if (nodeIsSynthesized(node) || !generatedBlockScopeNames) { - return undefined; + function getGeneratedNameForIdentifier(node: Identifier, allowGeneratedIdentifiers: boolean): string { + if (!nodeIsSynthesized(node)) { + if (allowGeneratedIdentifiers && generatedBlockScopeNames) { + var variableId = resolver.getBlockScopedVariableId(node) + if (variableId !== undefined) { + return generatedBlockScopeNames[variableId]; + } + } + if (languageVersion >= ScriptTarget.ES6 && generatedClassNames) { + var variableId = resolver.getClassDeclarationVariableId(node); + if (variableId !== undefined) { + return generatedClassNames[variableId]; + } + } } - - var variableId = resolver.getBlockScopedVariableId(node) - if (variableId === undefined) { - return undefined; - } - - return generatedBlockScopeNames[variableId]; + return undefined; } function emitIdentifier(node: Identifier, allowGeneratedIdentifiers: boolean) { - if (allowGeneratedIdentifiers) { - let generatedName = getGeneratedNameForIdentifier(node); - if (generatedName) { - write(generatedName); - return; - } + let generatedName = getGeneratedNameForIdentifier(node, allowGeneratedIdentifiers); + if (generatedName) { + write(generatedName); + return; } if (!node.parent) { write(node.text); @@ -3034,7 +3056,7 @@ module ts { function emitComputedPropertyName(node: ComputedPropertyName) { write("["); - emit(node.expression); + emitExpressionForPropertyName(node); write("]"); } @@ -3065,7 +3087,7 @@ module ts { if (languageVersion < ScriptTarget.ES6) { // Emit identifier as an identifier write(": "); - var generatedName = getGeneratedNameForIdentifier(node.name); + var generatedName = getGeneratedNameForIdentifier(node.name, /*allowGeneratedIdentifiers*/ true); if (generatedName) { write(generatedName); } @@ -4723,6 +4745,23 @@ module ts { emitLeadingComments(accessors.setAccessor); write("set: "); emitStart(accessors.setAccessor); + if (forEach(accessors.setAccessor.parameters, nodeIsDecorated)) { + if (!generatedSetterNames) { + generatedSetterNames = []; + } + + var preferredName: string; + if ((member).name.kind === SyntaxKind.Identifier) { + preferredName = "set_" + ((member).name).text; + } + + let generatedVariable = createTempVariable(node, preferredName); + let generatedName = generatedVariable.text; + recordTempDeclaration(generatedVariable); + generatedSetterNames[accessors.setAccessor.id] = generatedName; + write(generatedName); + write(" = "); + } write("function "); emitSignatureAndBody(accessors.setAccessor); emitEnd(accessors.setAccessor); @@ -4893,6 +4932,15 @@ module ts { tempParameters = saveTempParameters; } + function emitClassDeclaration(node: ClassDeclaration) { + if (languageVersion < ScriptTarget.ES6) { + emitClassDeclarationBelowES6(node); + } + else { + emitClassDeclarationForES6AndHigher(node); + } + } + function emitClassDeclarationForES6AndHigher(node: ClassDeclaration) { if (isES6ModuleMemberDeclaration(node)) { write("export "); @@ -4902,16 +4950,56 @@ module ts { } } + if (nodeOrChildIsDecorated(node)) { + if ((node.flags & NodeFlags.Default) === 0) { + write("let "); + emitDeclarationName(node); + write(" = "); + } + + write("(function() {"); + + var saveTempCount = tempCount; + var saveTempVariables = tempVariables; + var saveTempParameters = tempParameters; + var saveGeneratedComputedPropertyNames = generatedComputedPropertyNames; + var saveGeneratedSetterNames = generatedSetterNames; + tempCount = 0; + tempVariables = undefined; + tempParameters = undefined; + generatedComputedPropertyNames = undefined; + generatedSetterNames = undefined; + + increaseIndent(); + writeLine(); + + if (nodeIsDecorated(node)) { + if (!generatedClassNames) { + generatedClassNames = []; + } + + var generatedName = generateUniqueNameForLocation(node, "_" + node.name.text); + var variableId = resolver.getClassDeclarationVariableId(node.name); + write("let "); + write(generatedName); + write(" = "); + } + } + write("class "); // check if this is an "export default class" as it may not have a name if (node.name || !(node.flags & NodeFlags.Default)) { emitDeclarationName(node); + if (nodeIsDecorated(node)) { + generatedClassNames[variableId] = generatedName; + } } var baseTypeNode = getClassBaseTypeNode(node); if (baseTypeNode) { write(" extends "); emit(baseTypeNode.typeName); } + write(" {"); increaseIndent(); scopeEmitStart(node); @@ -4931,6 +5019,23 @@ module ts { writeLine(); emitMemberAssignments(node, NodeFlags.Static); + if (nodeOrChildIsDecorated(node)) { + writeLine(); + emitDecoratorsOfClass(node); + emitTempDeclarations(/*newLine*/ true); + writeLine(); + tempCount = saveTempCount; + tempVariables = saveTempVariables; + tempParameters = saveTempParameters; + generatedComputedPropertyNames = saveGeneratedComputedPropertyNames; + generatedSetterNames = saveGeneratedSetterNames; + + write("return "); + emitDeclarationName(node); + decreaseIndent(); + writeLine(); + write("})();"); + } // If this is an exported class, but not on the top level (i.e. on an internal // module), export it if (!isES6ModuleMemberDeclaration(node) && (node.flags & NodeFlags.Export)) { @@ -4953,6 +5058,16 @@ module ts { write("_super"); } write(") {"); + let saveTempCount = tempCount; + let saveTempVariables = tempVariables; + let saveTempParameters = tempParameters; + let saveGeneratedComputedPropertyNames = generatedComputedPropertyNames; + let saveGeneratedSetterNames = generatedSetterNames; + tempCount = 0; + tempVariables = undefined; + tempParameters = undefined; + generatedComputedPropertyNames = undefined; + generatedSetterNames = undefined; increaseIndent(); scopeEmitStart(node); if (baseTypeNode) { @@ -4968,6 +5083,14 @@ module ts { emitMemberFunctionsForES5AndLower(node); emitMemberAssignments(node, NodeFlags.Static); writeLine(); + emitDecoratorsOfClass(node); + emitTempDeclarations(/*newLine*/ true); + writeLine(); + tempCount = saveTempCount; + tempVariables = saveTempVariables; + tempParameters = saveTempParameters; + generatedComputedPropertyNames = saveGeneratedComputedPropertyNames; + generatedSetterNames = saveGeneratedSetterNames; emitToken(SyntaxKind.CloseBraceToken, node.members.end, () => { write("return "); emitDeclarationName(node); @@ -5000,6 +5123,172 @@ module ts { } } + function emitTargetOfClassElement(node: ClassDeclaration, member: Node) { + emitDeclarationName(node); + if (!(member.flags & NodeFlags.Static)) { + write(".prototype"); + } + } + + function getDecoratorsOfMember(node: ClassDeclaration, member: ClassElement) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.PropertyDeclaration: + return member.decorators; + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + let accessors = getAllAccessorDeclarations(node.members, member); + if (member === accessors.firstAccessor) { + let decorators = accessors.firstAccessor.decorators; + if (member !== accessors.lastAccessor && accessors.lastAccessor.decorators) { + if (decorators) { + return decorators.concat(accessors.lastAccessor.decorators); + } + return accessors.lastAccessor.decorators; + } + return decorators; + } + break; + } + return undefined; + } + + function emitExpressionOfDecorator(node: Decorator) { + let expression = node.expression; + emit(expression); + } + + function emitDecorateStart(decorators: Decorator[]): void { + write("__decorate(["); + let decoratorCount = decorators.length; + for (let i = 0; i < decoratorCount; i++) { + if (i > 0) { + write(", "); + } + let decorator = decorators[i]; + emitStart(decorator); + emitExpressionOfDecorator(decorator); + emitEnd(decorator); + } + write(`], `); + } + + function emitDecoratorsOfParameter(node: FunctionLikeDeclaration, parameter: ParameterDeclaration, parameterIndex: number) { + let decorators = parameter.decorators; + if (!decorators || decorators.length === 0) { + return; + } + + emitStart(node); + emitDecorateStart(decorators); + emitPropertyAccessForMethod(node); + write(", "); + write(String(parameterIndex)); + write(");"); + emitEnd(node); + writeLine(); + } + + function emitPropertyAccessForMethod(node: FunctionLikeDeclaration) { + if (node.parent && node.parent.kind === SyntaxKind.ClassDeclaration) { + if (node.kind === SyntaxKind.Constructor) { + emitDeclarationName(node.parent); + } + else if (node.kind === SyntaxKind.SetAccessor) { + if (generatedSetterNames) { + var generatedName = generatedSetterNames[node.id]; + if (generatedName) { + write(generatedName); + return; + } + } + + write("Object.getOwnPropertyDescriptor("); + emitTargetOfClassElement(node.parent, node); + write(", "); + emitExpressionForPropertyName(node.name); + write(").set"); + } + else { + emitTargetOfClassElement(node.parent, node); + emitMemberAccessForPropertyName(node.name); + } + } + } + + function emitDecoratorsOfParameters(node: FunctionLikeDeclaration) { + forEach(node.parameters, (parameter, parameterIndex) => emitDecoratorsOfParameter(node, parameter, parameterIndex)); + } + + function emitDecoratorsOfMembers(node: ClassDeclaration) { + forEach(node.members, member => emitDecoratorsOfMember(node, member)); + } + + function emitDecoratorsOfMember(node: ClassDeclaration, member: ClassElement) { + switch (member.kind) { + case SyntaxKind.MethodDeclaration: + emitDecoratorsOfParameters(member); + break; + + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + let accessors = getAllAccessorDeclarations(node.members, member); + if (member === accessors.firstAccessor && accessors.setAccessor) { + emitDecoratorsOfParameters(accessors.setAccessor); + } + break; + + case SyntaxKind.PropertyDeclaration: + break; + + default: + return; + } + + let name = member.name; + let decorators = getDecoratorsOfMember(node, member); + if (!decorators || decorators.length === 0) { + return; + } + + if (languageVersion >= ScriptTarget.ES5) { + emitStart(member); + emitDecorateStart(decorators); + emitTargetOfClassElement(node, member); + write(", "); + emitExpressionForPropertyName(name); + write(");"); + emitEnd(member); + writeLine(); + } + } + + function emitDecoratorsOfConstructor(node: ClassDeclaration) { + let constructor = getFirstConstructorWithBody(node); + if (constructor) { + emitDecoratorsOfParameters(constructor); + } + + let decorators = node.decorators; + if (!decorators || decorators.length === 0) { + return; + } + + emitStart(node); + emitDeclarationName(node); + write(" = "); + emitDecorateStart(node.decorators); + emitDeclarationName(node); + write(");"); + emitEnd(node); + writeLine(); + } + + function emitDecoratorsOfClass(node: ClassDeclaration) { + emitDecoratorsOfMembers(node); + emitDecoratorsOfConstructor(node); + } + function emitInterfaceDeclaration(node: InterfaceDeclaration) { emitPinnedOrTripleSlashComments(node); } @@ -5654,6 +5943,17 @@ module ts { return statements.length; } + function writeHelper(text: string): void { + let lines = text.split(/\r\n|\r|\n/g); + for (let i = 0; i < lines.length; ++i) { + let line = lines[i]; + if (line.length) { + writeLine(); + write(line); + } + } + } + function emitSourceFileNode(node: SourceFile) { // Start new file on new line writeLine(); @@ -5680,6 +5980,20 @@ module ts { write("};"); extendsEmitted = true; } + if (!decorateEmitted && resolver.getNodeCheckFlags(node) & NodeCheckFlags.EmitDecorate) { + writeHelper(` +var __decorate = this.__decorate || function (decorators, target, key) { + var kind = key == null ? 0 : typeof key == "number" ? 1 : 2, result = target; + if (kind == 2) result = Object.getOwnPropertyDescriptor(target, typeof key == "symbol" ? key : key = String(key)); + for (var i = decorators.length - 1; i >= 0; --i) { + var decorator = decorators[i]; + result = (kind == 0 ? decorator(result) : kind == 1 ? decorator(target, key) : decorator(target, key, result)) || result; + } + if (kind == 2 && result) Object.defineProperty(target, key, result); + if (kind == 0) return result; +};`); + decorateEmitted = true; + } if (isExternalModule(node)) { if (languageVersion >= ScriptTarget.ES6) { emitES6Module(node, startIndex); @@ -5897,7 +6211,7 @@ module ts { case SyntaxKind.VariableDeclaration: return emitVariableDeclaration(node); case SyntaxKind.ClassDeclaration: - return languageVersion < ScriptTarget.ES6 ? emitClassDeclarationBelowES6(node) : emitClassDeclarationForES6AndHigher(node); + return emitClassDeclaration(node); case SyntaxKind.InterfaceDeclaration: return emitInterfaceDeclaration(node); case SyntaxKind.EnumDeclaration: diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 02ca6627b2e..e2eeeeb5917 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1233,6 +1233,7 @@ module ts { getConstantValue(node: EnumMember | PropertyAccessExpression | ElementAccessExpression): number; isUnknownIdentifier(location: Node, name: string): boolean; getBlockScopedVariableId(node: Identifier): number; + getClassDeclarationVariableId(node: Identifier): number; } export const enum SymbolFlags { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 959476df2a6..a48a8fc7f2a 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -586,6 +586,78 @@ module ts { return forEach(node.members, getConstructorWithBody); } + export function nodeOrChildIsDecorated(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + if (node.decorators) { + return true; + } + + return forEach((node).members, nodeOrChildIsDecorated); + + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + if (node.decorators) { + return true; + } + + return false; + + case SyntaxKind.GetAccessor: + if ((node).body && node.decorators) { + return true; + } + + return false; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + if ((node).body && node.decorators) { + return true; + } + + return forEach((node).parameters, nodeOrChildIsDecorated); + } + + return false; + } + + export function nodeIsDecorated(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + if (node.decorators) { + return true; + } + + return false; + + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + if (node.decorators) { + return true; + } + + return false; + + case SyntaxKind.GetAccessor: + if ((node).body && node.decorators) { + return true; + } + + return false; + + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + if ((node).body && node.decorators) { + return true; + } + + return false; + } + + return false; + } + export function isExpression(node: Node): boolean { switch (node.kind) { case SyntaxKind.ThisKeyword: