diff --git a/Jakefile.js b/Jakefile.js index 44cb1a61dbe..69510545ea3 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -42,6 +42,14 @@ var compilerSources = [ "checker.ts", "factory.ts", "visitor.ts", + "transformers/destructuring.ts", + "transformers/ts.ts", + "transformers/module/es6.ts", + "transformers/module/system.ts", + "transformers/module/module.ts", + "transformers/jsx.ts", + "transformers/es7.ts", + "transformers/es6.ts", "transformer.ts", "sourcemap.ts", "comments.ts", @@ -67,6 +75,14 @@ var servicesSources = [ "checker.ts", "factory.ts", "visitor.ts", + "transformers/destructuring.ts", + "transformers/ts.ts", + "transformers/module/es6.ts", + "transformers/module/system.ts", + "transformers/module/module.ts", + "transformers/jsx.ts", + "transformers/es7.ts", + "transformers/es6.ts", "transformer.ts", "sourcemap.ts", "comments.ts", diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index d1101ad57e1..b65c05ed3ca 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1838,6 +1838,12 @@ namespace ts { transformFlags |= TransformFlags.AssertJsx; break; + case SyntaxKind.ExportKeyword: + // This node is both ES6 and TypeScript syntax. + transformFlags |= TransformFlags.AssertES6 | TransformFlags.TypeScript; + break; + + case SyntaxKind.DefaultKeyword: case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: case SyntaxKind.TemplateMiddle: diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 5f68777a63b..350a5165e66 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -142,25 +142,44 @@ namespace ts { return count; } - export function filter(array: T[], f: (x: T) => boolean): T[] { + export function filter(array: T[], f: (x: T, i: number) => x is U): U[]; + export function filter(array: T[], f: (x: T, i: number) => boolean): T[]; + export function filter(array: T[], f: (x: T, i: number) => boolean): T[] { let result: T[]; if (array) { result = []; - for (const item of array) { - if (f(item)) { - result.push(item); + for (let i = 0; i < array.length; i++) { + const v = array[i]; + if (f(v, i)) { + result.push(v); } } } return result; } - export function map(array: T[], f: (x: T) => U): U[] { + export function map(array: T[], f: (x: T, i: number) => U): U[] { let result: U[]; if (array) { result = []; - for (const v of array) { - result.push(f(v)); + for (let i = 0; i < array.length; i++) { + const v = array[i]; + result.push(f(v, i)); + } + } + return result; + } + + export function flatMap(array: T[], f: (x: T, i: number) => U[]): U[] { + let result: U[]; + if (array) { + result = []; + for (let i = 0; i < array.length; i++) { + const v = array[i]; + const ar = f(v, i); + if (ar) { + result = result.concat(ar); + } } } return result; @@ -212,15 +231,25 @@ namespace ts { return true; } + export function firstOrUndefined(array: T[]): T { + return array && array.length > 0 + ? array[0] + : undefined; + } + + export function singleOrUndefined(array: T[]): T { + return array && array.length === 1 + ? array[0] + : undefined; + } + /** * Returns the last element of an array if non-empty, undefined otherwise. */ export function lastOrUndefined(array: T[]): T { - if (array.length === 0) { - return undefined; - } - - return array[array.length - 1]; + return array && array.length > 0 + ? array[array.length - 1] + : undefined; } /** diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 4b1886fa848..ebac2996765 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -287,8 +287,12 @@ namespace ts { _i = 0x10000000, // Use/preference flag for '_i' } - // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { + return printFiles(resolver, host, targetSourceFile); + } + + // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature + export function legacyEmitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { // emit output for the __extends helper function const extendsHelper = ` var __extends = (this && this.__extends) || function (d, b) { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 242c67f51b4..35d017d3ca2 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -6,33 +6,88 @@ namespace ts { let NodeConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; let SourceFileConstructor: new (kind: SyntaxKind, pos: number, end: number) => Node; - function createNode(kind: SyntaxKind, location?: TextRange): Node { + function createNode(kind: SyntaxKind, location?: TextRange, flags?: NodeFlags): Node { const ConstructorForKind = kind === SyntaxKind.SourceFile ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); - return location + const node = location ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); + + if (flags) { + node.flags = flags; + } + + return node; } - export function createNodeArray(elements?: T[], pos?: number, end?: number): NodeArray { - const array = >(elements || []); - array.pos = pos; - array.end = end; + export function createNodeArray(elements?: T[], location?: TextRange): NodeArray { + if (elements) { + if (isNodeArray(elements)) { + return elements; + } + } + else { + elements = []; + } + + const array = >elements; + if (location) { + array.pos = location.pos; + array.end = location.end; + } + else { + array.pos = -1; + array.end = -1; + } + array.arrayKind = ArrayKind.NodeArray; return array; } - export function createModifiersArray(elements?: Modifier[], pos?: number, end?: number): ModifiersArray { - const array = (elements || []); - array.pos = pos; - array.end = end; + export function createModifiersArray(elements?: Modifier[], location?: TextRange): ModifiersArray { + let flags: NodeFlags; + if (elements) { + if (isModifiersArray(elements)) { + return elements; + } + + flags = 0; + for (const modifier of elements) { + flags |= modifierToFlag(modifier.kind); + } + } + else { + elements = []; + flags = 0; + } + + const array = elements; + if (location) { + array.pos = location.pos; + array.end = location.end; + } + else { + array.pos = -1; + array.end = -1; + } + array.arrayKind = ArrayKind.ModifiersArray; - array.flags = 0; + array.flags = flags; return array; } + function setModifiers(node: Node, modifiers: Modifier[]) { + if (modifiers) { + node.modifiers = createSynthesizedModifiersArray(modifiers); + node.flags |= node.modifiers.flags; + } + else { + node.modifiers = undefined; + } + } + export function createSynthesizedNode(kind: SyntaxKind, startsOnNewLine?: boolean): Node { const node = createNode(kind, /*location*/ undefined); node.startsOnNewLine = startsOnNewLine; @@ -40,11 +95,11 @@ namespace ts { } export function createSynthesizedNodeArray(elements?: T[]): NodeArray { - return createNodeArray(elements, /*pos*/ -1, /*end*/ -1); + return createNodeArray(elements, /*location*/ undefined); } export function createSynthesizedModifiersArray(elements?: Modifier[]): ModifiersArray { - return createModifiersArray(elements, /*pos*/ -1, /*end*/ -1); + return createModifiersArray(elements, /*location*/ undefined); } /** @@ -93,41 +148,28 @@ namespace ts { return node; } - export function createReturn(expression?: Expression): ReturnStatement { - const node = createSynthesizedNode(SyntaxKind.ReturnStatement); - node.expression = expression; - return node; + // Literals + + export function createLiteral(value: string): StringLiteral; + export function createLiteral(value: number): LiteralExpression; + export function createLiteral(value: string | number | boolean): PrimaryExpression; + export function createLiteral(value: string | number | boolean): T { + if (typeof value === "number") { + const node = createNode(SyntaxKind.NumericLiteral); + node.text = value.toString(); + return node; + } + else if (typeof value === "boolean") { + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); + } + else { + const node = createNode(SyntaxKind.StringLiteral); + node.text = String(value); + return node; + } } - export function createStatement(expression: Expression): ExpressionStatement { - const node = createSynthesizedNode(SyntaxKind.ExpressionStatement); - node.expression = expression; - return node; - } - - export function createVariableStatement(declarationList: VariableDeclarationList): VariableStatement { - const node = createSynthesizedNode(SyntaxKind.VariableStatement); - node.declarationList = declarationList; - return node; - } - - export function createVariableDeclarationList(declarations: VariableDeclaration[]): VariableDeclarationList { - const node = createSynthesizedNode(SyntaxKind.VariableDeclarationList); - node.declarations = createNodeArray(declarations); - return node; - } - - export function createBlock(statements: Statement[]): Block { - const block = createSynthesizedNode(SyntaxKind.Block); - block.statements = createNodeArray(statements); - return block; - } - export function createVariableDeclaration(name: BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { - const node = createNode(SyntaxKind.VariableDeclaration, location); - node.name = name; - node.initializer = initializer; - return node; - } + // Identifiers export function createIdentifier(text: string): Identifier { const node = createNode(SyntaxKind.Identifier); @@ -151,33 +193,102 @@ namespace ts { return name; } - export function createLiteral(value: string): StringLiteral; - export function createLiteral(value: number): LiteralExpression; - export function createLiteral(value: string | number | boolean): PrimaryExpression; - export function createLiteral(value: string | number | boolean): T { - if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral); - node.text = value.toString(); - return node; - } - else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword); - } - else { - const node = createNode(SyntaxKind.StringLiteral); - node.text = String(value); - return node; - } + // Reserved words + + export function createSuper() { + const node = createNode(SyntaxKind.SuperKeyword); + return node; } - export function createVoid(expression: UnaryExpression) { - const node = createNode(SyntaxKind.VoidExpression); + export function createThis() { + const node = createNode(SyntaxKind.ThisKeyword); + return node; + } + + export function createNull() { + const node = createNode(SyntaxKind.NullKeyword); + return node; + } + + // Names + + export function createComputedPropertyName(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ComputedPropertyName, location); node.expression = expression; return node; } - export function createVoidZero() { - return createVoid(createLiteral(0)); + // Type members + + export function createMethod(modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.MethodDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = coercePropertyName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.body = body; + return node; + } + + export function createConstructor(parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.Constructor, location); + node.decorators = undefined; + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createSynthesizedNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createGetAccessor(modifiers: Modifier[], name: string | PropertyName, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.GetAccessor, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = coercePropertyName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(); + node.body = body; + return node; + } + + export function createSetAccessor(modifiers: Modifier[], name: string | PropertyName, parameter: ParameterDeclaration, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.SetAccessor, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = coercePropertyName(name); + node.typeParameters = undefined; + node.parameters = createNodeArray([parameter]); + node.body = body; + return node; + } + + export function createParameter(name: string | Identifier | BindingPattern, initializer?: Expression) { + const node = createNode(SyntaxKind.Parameter); + node.decorators = undefined; + node.modifiers = undefined; + node.dotDotDotToken = undefined; + node.name = coerceBindingName(name); + node.questionToken = undefined; + node.type = undefined; + node.initializer = initializer; + return node; + } + + + // Expression + + export function createArrayLiteral(elements?: Expression[]) { + const node = createNode(SyntaxKind.ArrayLiteralExpression); + node.elements = createNodeArray(elements); + return node; + } + + export function createObjectLiteral(properties?: ObjectLiteralElement[]) { + const node = createNode(SyntaxKind.ObjectLiteralExpression); + node.properties = createNodeArray(properties); + return node; } export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { @@ -195,13 +306,51 @@ namespace ts { return node; } - export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression) { - const node = createNode(SyntaxKind.ConditionalExpression); - node.condition = condition; - node.questionToken = createSynthesizedNode(SyntaxKind.QualifiedName); - node.whenTrue = whenTrue; - node.colonToken = createSynthesizedNode(SyntaxKind.ColonToken); - node.whenFalse = whenFalse; + export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.CallExpression, location); + node.expression = parenthesizeForAccess(expression); + node.arguments = createNodeArray(argumentsArray); + return node; + } + + export function createParen(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ParenthesizedExpression, location); + node.expression = expression; + return node; + } + + export function createFunctionExpression(asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionExpression, location); + node.modifiers = undefined; + node.asteriskToken = asteriskToken; + node.name = coerceIdentifier(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createArrowFunction(parameters: ParameterDeclaration[], body: Expression | Block, location?: TextRange) { + const node = createNode(SyntaxKind.ArrowFunction, location); + node.modifiers = undefined; + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.equalsGreaterThanToken = createNode(SyntaxKind.EqualsGreaterThanToken); + node.body = body; + return node; + } + + export function createTypeOf(expression: Expression) { + const node = createNode(SyntaxKind.TypeOfExpression); + node.expression = parenthesizeForUnary(expression); + return node; + } + + export function createVoid(expression: Expression) { + const node = createNode(SyntaxKind.VoidExpression); + node.expression = parenthesizeForUnary(expression); return node; } @@ -213,22 +362,194 @@ namespace ts { return node; } + export function createConditional(condition: Expression, whenTrue: Expression, whenFalse: Expression) { + const node = createNode(SyntaxKind.ConditionalExpression); + node.condition = condition; + node.questionToken = createSynthesizedNode(SyntaxKind.QualifiedName); + node.whenTrue = whenTrue; + node.colonToken = createSynthesizedNode(SyntaxKind.ColonToken); + node.whenFalse = whenFalse; + return node; + } + + export function createYield(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.YieldExpression, location); + node.expression = expression; + return node; + } + + export function createSpread(expression: Expression) { + const node = createNode(SyntaxKind.SpreadElementExpression); + node.expression = expression; + return node; + } + + export function createClassExpression(name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassExpression, location); + node.decorators = undefined; + node.modifiers = undefined; + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createSynthesizedNodeArray(heritageClauses); + node.members = createSynthesizedNodeArray(members); + return node; + } + + export function createExpressionWithTypeArguments(expression: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.ExpressionWithTypeArguments, location); + node.typeArguments = undefined; + node.expression = parenthesizeForAccess(expression); + return node; + } + + // Element + + export function createBlock(statements: Statement[], location?: TextRange): Block { + const block = createNode(SyntaxKind.Block, location); + block.statements = createNodeArray(statements); + return block; + } + + export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList, location?: TextRange): VariableStatement { + const node = createNode(SyntaxKind.VariableStatement, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.declarationList = declarationList; + return node; + } + + export function createVariableDeclarationList(declarations: VariableDeclaration[], location?: TextRange, flags?: NodeFlags): VariableDeclarationList { + const node = createNode(SyntaxKind.VariableDeclarationList, location, flags); + node.declarations = createNodeArray(declarations); + return node; + } + + export function createLetDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Let); + } + + export function createConstDeclarationList(declarations: VariableDeclaration[], location?: TextRange) { + return createVariableDeclarationList(declarations, location, NodeFlags.Const); + } + + export function createVariableDeclaration(name: string | BindingPattern | Identifier, initializer?: Expression, location?: TextRange): VariableDeclaration { + const node = createNode(SyntaxKind.VariableDeclaration, location); + node.name = coerceBindingName(name); + node.initializer = initializer; + return node; + } + + export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { + const node = createNode(SyntaxKind.ExpressionStatement, location); + node.expression = expression; + return node; + } + + export function createReturn(expression?: Expression): ReturnStatement { + const node = createSynthesizedNode(SyntaxKind.ReturnStatement); + node.expression = expression; + return node; + } + + export function createFunctionDeclaration(modifiers: Modifier[], asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.asteriskToken = asteriskToken; + node.name = coerceIdentifier(name); + node.typeParameters = undefined; + node.parameters = createNodeArray(parameters); + node.type = undefined; + node.body = body; + return node; + } + + export function createClassDeclaration(modifiers: Modifier[], name: Identifier, heritageClauses: HeritageClause[], members: ClassElement[], location?: TextRange) { + const node = createNode(SyntaxKind.ClassDeclaration, location); + node.decorators = undefined; + setModifiers(node, modifiers); + node.name = name; + node.typeParameters = undefined; + node.heritageClauses = createSynthesizedNodeArray(heritageClauses); + node.members = createSynthesizedNodeArray(members); + return node; + } + + export function createExportDefault(expression: Expression) { + const node = createNode(SyntaxKind.ExportAssignment); + node.isExportEquals = false; + node.expression = expression; + return node; + } + + export function createExportDeclaration(exportClause: NamedExports, moduleSpecifier?: Expression) { + const node = createNode(SyntaxKind.ExportDeclaration); + node.exportClause = exportClause; + node.moduleSpecifier = moduleSpecifier; + return node; + } + + export function createNamedExports(elements: ExportSpecifier[]) { + const node = createNode(SyntaxKind.NamedExports); + node.elements = createNodeArray(elements); + return node; + } + + export function createExportSpecifier(name: string | Identifier, propertyName?: string | Identifier) { + const node = createNode(SyntaxKind.ExportSpecifier); + node.name = coerceIdentifier(name); + node.propertyName = coerceIdentifier(propertyName); + return node; + } + + // Clauses + + export function createHeritageClause(token: SyntaxKind, types: ExpressionWithTypeArguments[], location?: TextRange) { + const node = createNode(SyntaxKind.HeritageClause, location); + node.token = token; + node.types = createSynthesizedNodeArray(types); + return node; + } + + // Compound nodes + export function createAssignment(left: Expression, right: Expression, location?: TextRange) { return createBinary(left, SyntaxKind.EqualsToken, right, location); } + export function createLogicalAnd(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.AmpersandAmpersandToken, right); + } + + export function createLogicalOr(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.BarBarToken, right); + } + export function createStrictEquality(left: Expression, right: Expression) { return createBinary(left, SyntaxKind.EqualsEqualsEqualsToken, right); } + export function createStrictInequality(left: Expression, right: Expression) { + return createBinary(left, SyntaxKind.ExclamationEqualsEqualsToken, right); + } + export function createComma(left: Expression, right: Expression) { return createBinary(left, SyntaxKind.CommaToken, right); } - export function createCall(expression: Expression, argumentsArray: Expression[], location?: TextRange) { - const node = createNode(SyntaxKind.CallExpression, location); - node.expression = parenthesizeForAccess(expression); - node.arguments = createNodeArray(argumentsArray); + export function createVoidZero() { + return createVoid(createLiteral(0)); + } + + export function createMemberAccessForPropertyName(target: Expression, memberName: PropertyName, location?: TextRange): MemberExpression { + return isIdentifier(memberName) + ? createPropertyAccess(target, cloneNode(memberName), location) + : createElementAccess(target, cloneNode(isComputedPropertyName(memberName) ? memberName.expression : memberName), location); + } + + export function createRestParameter(name: string | Identifier) { + const node = createParameter(name, /*initializer*/ undefined); + node.dotDotDotToken = createSynthesizedNode(SyntaxKind.DotDotDotToken); return node; } @@ -245,16 +566,232 @@ namespace ts { ); } - export function parenthesizeExpression(expression: Expression) { - const node = createNode(SyntaxKind.ParenthesizedExpression); - node.expression = expression; - return node; + // Helpers + + export function createParamHelper(expression: Expression, parameterOffset: number) { + return createCall( + createIdentifier("__param"), + [ + createLiteral(parameterOffset), + expression + ] + ); + } + + export function createMetadataHelper(metadataKey: string, metadataValue: Expression, defer?: boolean) { + return createCall( + createIdentifier("__metadata"), + [ + createLiteral(metadataKey), + defer + ? createArrowFunction([], metadataValue) + : metadataValue + ] + ); + } + + export function createDecorateHelper(decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression) { + const argumentsArray: Expression[] = []; + argumentsArray.push(createArrayLiteral(decoratorExpressions)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); + } + } + + return createCall(createIdentifier("__decorate"), argumentsArray); + } + + export function createAwaiterHelper(hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + return createCall( + createIdentifier("__awaiter"), + [ + createThis(), + hasLexicalArguments ? createIdentifier("arguments") : createVoidZero(), + promiseConstructor ? createExpressionFromEntityName(promiseConstructor) : createVoidZero(), + createFunctionExpression( + createNode(SyntaxKind.AsteriskToken), + /*name*/ undefined, + [], + body + ) + ] + ); + } + + function createObjectCreate(prototype: Expression) { + return createCall( + createPropertyAccess(createIdentifier("Object"), "create"), + [prototype] + ); + } + + function createGeti(target: LeftHandSideExpression) { + // name => super[name] + return createArrowFunction( + [createParameter("name")], + createElementAccess( + target, + createIdentifier("name") + ) + ) + } + + function createSeti(target: LeftHandSideExpression) { + // (name, value) => super[name] = value + return createArrowFunction( + [ + createParameter("name"), + createParameter("value") + ], + createAssignment( + createElementAccess( + target, + createIdentifier("name") + ), + createIdentifier("value") + ) + ); + } + + export function createAdvancedAsyncSuperHelper() { + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + + // const cache = Object.create(null); + const createCache = createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "cache", + createObjectCreate(createNull()) + ) + ]) + ); + + // get value() { return geti(name); } + const getter = createGetAccessor( + /*modifiers*/ undefined, + "value", + createBlock([ + createReturn( + createCall( + createIdentifier("geti"), + [createIdentifier("name")] + ) + ) + ]) + ); + + // set value(v) { seti(name, v); } + const setter = createSetAccessor( + /*modifiers*/ undefined, + "value", + createParameter("v"), + createBlock([ + createStatement( + createCall( + createIdentifier("seti"), + [ + createIdentifier("name"), + createIdentifier("v") + ] + ) + ) + ]) + ); + + // return name => cache[name] || ... + const getOrCreateAccessorsForName = createReturn( + createArrowFunction( + [createParameter("name")], + createLogicalOr( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createParen( + createAssignment( + createElementAccess( + createIdentifier("cache"), + createIdentifier("name") + ), + createObjectLiteral([ + getter, + setter + ]) + ) + ) + ) + ) + ); + + // const _super = (function (geti, seti) { + // const cache = Object.create(null); + // return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); + // })(name => super[name], (name, value) => super[name] = value); + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createCall( + createParen( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [ + createParameter("geti"), + createParameter("seti") + ], + createBlock([ + createCache, + getOrCreateAccessorsForName + ]) + ) + ), + [ + createGeti(createSuper()), + createSeti(createSuper()) + ] + ) + ) + ]) + ); + } + + export function createSimpleAsyncSuperHelper() { + return createVariableStatement( + /*modifiers*/ undefined, + createConstDeclarationList([ + createVariableDeclaration( + "_super", + createGeti(createSuper()) + ) + ]) + ); } export function inlineExpressions(expressions: Expression[]) { return reduceLeft(expressions, createComma); } + export function createExpressionFromEntityName(node: EntityName | Expression): Expression { + return isQualifiedName(node) + ? createPropertyAccess( + createExpressionFromEntityName(node.left), + cloneNode(node.right) + ) + : cloneNode(node); + } + + + // Utilities + function coerceIdentifier(value: string | Identifier) { if (typeof value === "string") { return createIdentifier(value); @@ -264,6 +801,24 @@ namespace ts { } } + function coerceBindingName(value: string | BindingName) { + if (typeof value === "string") { + return createIdentifier(value); + } + else { + return value; + } + } + + function coercePropertyName(value: string | PropertyName) { + if (typeof value === "string") { + return createIdentifier(value); + } + else { + return value; + } + } + function coerceExpression(value: string | number | boolean | Expression): Expression { if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") { return createLiteral(value); @@ -291,7 +846,7 @@ namespace ts { } return needsParenthesesForBinary(operand, operator, side) - ? parenthesizeExpression(operand) + ? createParen(operand) : operand; } @@ -343,6 +898,42 @@ namespace ts { return expr; } - return parenthesizeExpression(expr); + return createParen(expr); + } + + function parenthesizeForUnary(operand: Expression) { + if (isUnaryExpression(operand)) { + return operand; + } + + return createParen(operand); + } + + + export function startOnNewLine(node: T): T { + (node).startsOnNewLine = true; + return node; + } + + export function setOriginalNode(node: T, original: Node): T { + node.original = original; + return node; + } + + export function setTextRange(node: T, location: TextRange): T { + if (location) { + node.pos = location.pos; + node.end = location.end; + } + return node; + } + + export function setNodeFlags(node: T, flags: NodeFlags): T { + node.flags = flags; + return node; + } + + export function getSynthesizedNode(node: T): T { + return nodeIsSynthesized(node) ? node : cloneNode(node, /*location*/ undefined, node.flags, /*parent*/ undefined, /*original*/ node); } } \ No newline at end of file diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 150ddb6a918..51129506e89 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -75,6 +75,15 @@ function __export(m) { } })`; + const superHelper = ` +const _super = name => super[name];`; + + const advancedSuperHelper = ` +const _super = (function (geti, seti) { + const cache = Object.create(null); + return name => cache[name] || (cache[name] = { get value() { return geti(name); }, set value(v) { seti(name, v); } }); +})(name => super[name], (name, value) => super[name] = value);`; + const compilerOptions = host.getCompilerOptions(); const languageVersion = getLanguageVersion(compilerOptions); const moduleKind = getModuleKind(compilerOptions); @@ -122,8 +131,12 @@ function __export(m) { let startLexicalEnvironment: () => void; let endLexicalEnvironment: () => Statement[]; let getNodeEmitFlags: (node: Node) => NodeEmitFlags; + let isExpressionSubstitutionEnabled: (node: Node) => boolean; + let isEmitNotificationEnabled: (node: Node) => boolean; let expressionSubstitution: (node: Expression) => Expression; let identifierSubstitution: (node: Identifier) => Identifier; + let onBeforeEmitNode: (node: Node) => void; + let onAfterEmitNode: (node: Node) => void; let isUniqueName: (name: string) => boolean; let temporaryVariables: string[] = []; let tempFlags: TempFlags; @@ -177,8 +190,12 @@ function __export(m) { startLexicalEnvironment = undefined; endLexicalEnvironment = undefined; getNodeEmitFlags = undefined; + isExpressionSubstitutionEnabled = undefined; + isEmitNotificationEnabled = undefined; expressionSubstitution = undefined; identifierSubstitution = undefined; + onBeforeEmitNode = undefined; + onAfterEmitNode = undefined; isUniqueName = undefined; temporaryVariables = undefined; tempFlags = 0; @@ -196,8 +213,12 @@ function __export(m) { startLexicalEnvironment = context.startLexicalEnvironment; endLexicalEnvironment = context.endLexicalEnvironment; getNodeEmitFlags = context.getNodeEmitFlags; + isExpressionSubstitutionEnabled = context.isExpressionSubstitutionEnabled; + isEmitNotificationEnabled = context.isEmitNotificationEnabled; expressionSubstitution = context.expressionSubstitution; identifierSubstitution = context.identifierSubstitution; + onBeforeEmitNode = context.onBeforeEmitNode; + onAfterEmitNode = context.onAfterEmitNode; isUniqueName = context.isUniqueName; return printSourceFile; } @@ -213,6 +234,11 @@ function __export(m) { function emit(node: Node) { if (node) { + const adviseOnEmit = isEmitNotificationEnabled(node); + if (adviseOnEmit && onBeforeEmitNode) { + onBeforeEmitNode(node); + } + const leadingComments = getLeadingCommentsToEmit(node); const trailingComments = getTrailingCommentsToEmit(node); emitLeadingComments(node, leadingComments); @@ -220,6 +246,10 @@ function __export(m) { emitWorker(node); emitEnd(node); emitTrailingComments(node, trailingComments); + + if (adviseOnEmit && onAfterEmitNode) { + onAfterEmitNode(node); + } } } @@ -234,7 +264,11 @@ function __export(m) { // Identifiers case SyntaxKind.Identifier: - return emitIdentifier(node, identifierSubstitution); + if (tryEmitSubstitute(node, identifierSubstitution)) { + return; + } + + return emitIdentifier(node); // Reserved words case SyntaxKind.ConstKeyword: @@ -486,6 +520,10 @@ function __export(m) { function emitExpressionWorker(node: Node) { const kind = node.kind; + if (isExpressionSubstitutionEnabled(node) && tryEmitSubstitute(node, expressionSubstitution)) { + return; + } + switch (kind) { // Literals case SyntaxKind.NumericLiteral: @@ -496,16 +534,15 @@ function __export(m) { // Identifiers case SyntaxKind.Identifier: - return emitIdentifier(node, expressionSubstitution); + return emitIdentifier(node); // Reserved words case SyntaxKind.FalseKeyword: case SyntaxKind.NullKeyword: case SyntaxKind.SuperKeyword: case SyntaxKind.TrueKeyword: - return writeTokenNode(node); case SyntaxKind.ThisKeyword: - return emitThisKeyword(node); + return writeTokenNode(node); // Expressions case SyntaxKind.ArrayLiteralExpression: @@ -597,11 +634,8 @@ function __export(m) { // Identifiers // - function emitIdentifier(node: Identifier, substitution: (node: Node) => Node) { - if (tryEmitSubstitute(node, substitution)) { - return; - } - else if (node.text === undefined) { + function emitIdentifier(node: Identifier) { + if (node.text === undefined) { // Emit a temporary variable name for this node. const nodeId = getOriginalNodeId(node); const text = temporaryVariables[nodeId] || (temporaryVariables[nodeId] = makeTempVariableName(tempKindToFlags(node.tempKind))); @@ -620,15 +654,6 @@ function __export(m) { } } - function emitThisKeyword(node: PrimaryExpression) { - if (tryEmitSubstitute(node, expressionSubstitution)) { - return; - } - else { - writeTokenNode(node); - } - } - // // Names // @@ -1051,19 +1076,11 @@ function __export(m) { } function emitPostfixUnaryExpression(node: PostfixUnaryExpression) { - if (tryEmitSubstitute(node, expressionSubstitution)) { - return; - } - emitExpression(node.operand); writeToken(node.operator); } function emitBinaryExpression(node: BinaryExpression) { - if (tryEmitSubstitute(node, expressionSubstitution)) { - return; - } - const isCommaOperator = node.operatorToken.kind !== SyntaxKind.CommaToken; const indentBeforeOperator = needsIndentation(node, node.left, node.operatorToken); const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right); @@ -1390,9 +1407,12 @@ function __export(m) { function emitBlockFunctionBody(parentNode: Node, body: Block) { // Emit all the prologue directives (like "use strict"). + increaseIndent(); const statements = body.statements; - const statementOffset = emitPrologueDirectives(statements, /*startWithNewLine*/ true, /*indented*/ true); + const statementOffset = emitPrologueDirectives(statements, /*startWithNewLine*/ true); const helpersEmitted = emitHelpers(body); + decreaseIndent(); + if (statementOffset === 0 && !helpersEmitted && shouldEmitBlockFunctionBodyOnSingleLine(parentNode, body)) { emitList(body, statements, ListFormat.SingleLineFunctionBodyStatements); } @@ -1688,6 +1708,7 @@ function __export(m) { function emitHeritageClause(node: HeritageClause) { emitStart(node); + write(" "); writeToken(node.token); write(" "); emitList(node, node.types, ListFormat.HeritageClauseTypes); @@ -1783,8 +1804,7 @@ function __export(m) { } } - function emitPrologueDirectives(statements: Node[], startWithNewLine?: boolean, indented?: boolean) { - increaseIndentIf(indented); + function emitPrologueDirectives(statements: Node[], startWithNewLine?: boolean) { for (let i = 0; i < statements.length; i++) { if (isPrologueDirective(statements[i])) { if (startWithNewLine || i > 0) { @@ -1794,24 +1814,32 @@ function __export(m) { } else { // return index of the first non prologue directive - decreaseIndentIf(indented); return i; } } - decreaseIndentIf(indented); return statements.length; } function emitHelpers(node: Node) { const emitFlags = getNodeEmitFlags(node); let helpersEmitted = false; - if (emitFlags & NodeEmitFlags.EmitHelpers) { + if (emitFlags & NodeEmitFlags.EmitEmitHelpers) { helpersEmitted = emitEmitHelpers(currentSourceFile); } if (emitFlags & NodeEmitFlags.EmitExportStar) { - emitExportStarHelper(); + writeLines(exportStarHelper); + helpersEmitted = true; + } + + if (emitFlags & NodeEmitFlags.EmitSuperHelper) { + writeLines(superHelper); + helpersEmitted = true; + } + + if (emitFlags & NodeEmitFlags.EmitAdvancedSuperHelper) { + writeLines(advancedSuperHelper); helpersEmitted = true; } @@ -1861,10 +1889,6 @@ function __export(m) { return helpersEmitted; } - function emitExportStarHelper() { - writeLines(exportStarHelper); - } - function writeLines(text: string): void { const lines = text.split(/\r\n|\r|\n/g); for (let i = 0; i < lines.length; i++) { diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index 3e6e1dcf6e5..7552efddb21 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -56,6 +56,9 @@ namespace ts { const nodeEmitFlags: NodeEmitFlags[] = []; const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; + const enabledExpressionSubstitutions = new Array(SyntaxKind.Count); + const enabledEmitNotifications = new Array(SyntaxKind.Count); + let lexicalEnvironmentStackOffset = 0; let hoistedVariableDeclarations: VariableDeclaration[]; let hoistedFunctionDeclarations: FunctionDeclaration[]; @@ -75,7 +78,11 @@ namespace ts { hoistVariableDeclaration, hoistFunctionDeclaration, startLexicalEnvironment, - endLexicalEnvironment + endLexicalEnvironment, + enableExpressionSubstitution, + isExpressionSubstitutionEnabled, + enableEmitNotification, + isEmitNotificationEnabled, }; // Chain together and initialize each transformer. @@ -100,6 +107,23 @@ namespace ts { return visited; } + function enableExpressionSubstitution(kind: SyntaxKind) { + enabledExpressionSubstitutions[kind] = true; + } + + function isExpressionSubstitutionEnabled(node: Node) { + return enabledExpressionSubstitutions[node.kind]; + } + + function enableEmitNotification(kind: SyntaxKind) { + enabledEmitNotifications[kind] = true; + } + + function isEmitNotificationEnabled(node: Node) { + return enabledEmitNotifications[node.kind] + || (getNodeEmitFlags(node) & NodeEmitFlags.AdviseOnEmitNode) !== 0; + } + /** * Gets flags that control emit behavior of a node. */ @@ -207,7 +231,7 @@ namespace ts { case SyntaxKind.ClassExpression: return generateNameForClassExpression(); default: - return createTempVariable(TempVariableKind.Auto); + return createTempVariable(); } } @@ -289,6 +313,7 @@ namespace ts { if (hoistedVariableDeclarations) { statements.push( createVariableStatement( + /*modifiers*/ undefined, createVariableDeclarationList(hoistedVariableDeclarations) ) ); diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 19c25e5699f..c21d81ac4d5 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -10,8 +10,14 @@ namespace ts { * @param needsValue Indicates whether the value from the right-hand-side of the * destructuring assignment is needed as part of a larger expression. * @param recordTempVariable A callback used to record new temporary variables. + * @param visitor An optional visitor to use to visit expressions. */ - export function flattenDestructuringAssignment(node: BinaryExpression, needsValue: boolean, recordTempVariable: (node: Identifier) => void) { + export function flattenDestructuringAssignment( + node: BinaryExpression, + needsValue: boolean, + recordTempVariable: (node: Identifier) => void, + visitor?: (node: Node) => Node) { + let location: TextRange = node; let value = node.right; if (isEmptyObjectLiteralOrArrayLiteral(node.left)) { @@ -31,7 +37,7 @@ namespace ts { location = node.right; } - flattenDestructuring(node, value, location, emitAssignment, emitTempVariableAssignment); + flattenDestructuring(node, value, location, emitAssignment, emitTempVariableAssignment, visitor); if (needsValue) { expressions.push(value); @@ -64,11 +70,12 @@ namespace ts { * * @param node The ParameterDeclaration to flatten. * @param value The rhs value for the binding pattern. + * @param visitor An optional visitor to use to visit expressions. */ - export function flattenParameterDestructuring(node: ParameterDeclaration, value: Expression) { + export function flattenParameterDestructuring(node: ParameterDeclaration, value: Expression, visitor?: (node: Node) => Node) { const declarations: VariableDeclaration[] = []; - flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment); + flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); return declarations; @@ -94,11 +101,12 @@ namespace ts { * * @param node The VariableDeclaration to flatten. * @param value An optional rhs value for the binding pattern. + * @param visitor An optional visitor to use to visit expressions. */ - export function flattenVariableDestructuring(node: VariableDeclaration, value?: Expression) { + export function flattenVariableDestructuring(node: VariableDeclaration, value?: Expression, visitor?: (node: Node) => Node) { const declarations: VariableDeclaration[] = []; - flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment); + flattenDestructuring(node, value, node, emitAssignment, emitTempVariableAssignment, visitor); return declarations; @@ -129,8 +137,15 @@ namespace ts { * * @param node The VariableDeclaration to flatten. * @param recordTempVariable A callback used to record new temporary variables. + * @param nameSubstitution An optional callback used to substitute binding names. + * @param visitor An optional visitor to use to visit expressions. */ - export function flattenVariableDestructuringToExpression(node: VariableDeclaration, recordTempVariable: (name: Identifier) => void) { + export function flattenVariableDestructuringToExpression( + node: VariableDeclaration, + recordTempVariable: (name: Identifier) => void, + nameSubstitution?: (name: Identifier) => Expression, + visitor?: (node: Node) => Node) { + const pendingAssignments: Expression[] = []; flattenDestructuring(node, /*value*/ undefined, node, emitAssignment, emitTempVariableAssignment); @@ -140,6 +155,18 @@ namespace ts { return expression; function emitAssignment(name: Identifier, value: Expression, location: TextRange, original: Node) { + const left = nameSubstitution && nameSubstitution(name) || name; + emitPendingAssignment(left, value, location, original); + } + + function emitTempVariableAssignment(value: Expression, location: TextRange) { + const name = createTempVariable(); + recordTempVariable(name); + emitPendingAssignment(name, value, location, /*original*/ undefined); + return name; + } + + function emitPendingAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { const expression = createAssignment(name, value, location); if (isSimpleExpression(value)) { (expression).disableSourceMap = true; @@ -147,13 +174,7 @@ namespace ts { expression.original = original; pendingAssignments.push(expression); - } - - function emitTempVariableAssignment(value: Expression, location: TextRange) { - const name = createTempVariable(); - recordTempVariable(name); - emitAssignment(name, value, location, /*original*/ undefined); - return name; + return expression; } } @@ -162,7 +183,12 @@ namespace ts { value: Expression, location: TextRange, emitAssignment: (name: Identifier, value: Expression, location: TextRange, original: Node) => void, - emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier) { + emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, + visitor?: (node: Node) => Node) { + if (value && visitor) { + value = visitNode(value, visitor, isExpression); + } + if (isBinaryExpression(root)) { emitDestructuringAssignment(root.left, value, location) } @@ -174,14 +200,22 @@ namespace ts { // When emitting target = value use source map node to highlight, including any temporary assignments needed for this let target: Expression; if (isShortHandPropertyAssignment(bindingTarget)) { - if (bindingTarget.objectAssignmentInitializer) { - value = createDefaultValueCheck(value, bindingTarget.objectAssignmentInitializer, location); + const initializer = visitor + ? visitNode(bindingTarget.objectAssignmentInitializer, visitor, isExpression) + : bindingTarget.objectAssignmentInitializer; + + if (initializer) { + value = createDefaultValueCheck(value, initializer, location); } target = bindingTarget.name; } else if (isBinaryExpression(bindingTarget) && bindingTarget.operatorToken.kind === SyntaxKind.EqualsToken) { - value = createDefaultValueCheck(value, bindingTarget.right, location); + const initializer = visitor + ? visitNode(bindingTarget.right, visitor, isExpression) + : bindingTarget.right; + + value = createDefaultValueCheck(value, initializer, location); target = bindingTarget.left; } else { @@ -244,9 +278,10 @@ namespace ts { function emitBindingElement(target: BindingElement, value: Expression) { // Any temporary assignments needed to emit target = value should point to target - if (target.initializer) { + const initializer = visitor ? visitNode(target.initializer, visitor, isExpression) : target.initializer; + if (initializer) { // Combine value and initializer - value = value ? createDefaultValueCheck(value, target.initializer, target) : target.initializer; + value = value ? createDefaultValueCheck(value, initializer, target) : initializer; } else if (!value) { // Use 'void 0' in absence of value and initializer diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 0156bb5ff84..38e7a639b1d 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -1,30 +1,2737 @@ /// /// +/// /*@internal*/ namespace ts { + type SuperContainer = ClassDeclaration | MethodDeclaration | GetAccessorDeclaration | SetAccessorDeclaration | ConstructorDeclaration; + // TODO(rbuckton): TS->ES7 transformer export function transformTypeScript(context: TransformationContext) { + const { + nodeHasGeneratedName, + getGeneratedNameForNode, + makeUniqueName, + setNodeEmitFlags, + startLexicalEnvironment, + endLexicalEnvironment, + hoistVariableDeclaration, + } = context; + + const resolver = context.getEmitResolver(); + const compilerOptions = context.getCompilerOptions(); + const languageVersion = getLanguageVersion(compilerOptions); + const decoratedClassAliases: Map = {}; + const currentDecoratedClassAliases: Map = {}; + const previousExpressionSubstitution = context.expressionSubstitution; + const previousOnBeforeEmitNode = context.onBeforeEmitNode; + const previousOnAfterEmitNode = context.onAfterEmitNode; + context.enableExpressionSubstitution(SyntaxKind.Identifier); + context.expressionSubstitution = substituteExpression; + context.onBeforeEmitNode = onBeforeEmitNode; + context.onAfterEmitNode = onAfterEmitNode; + + let hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = false; + let currentSourceFile: SourceFile; + let currentNamespace: ModuleDeclaration; + let currentNamespaceLocalName: Identifier; + let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; + let currentParent: Node; + let currentNode: Node; + let isRightmostExpression: boolean; + let superContainerStack: SuperContainer[]; + return transformSourceFile; function transformSourceFile(node: SourceFile) { - return visitEachChild(node, visitor, context); + currentSourceFile = node; + node = visitEachChild(node, visitor, context); + setNodeEmitFlags(node, NodeEmitFlags.EmitEmitHelpers); + currentSourceFile = undefined; + return node; } - function visitor(node: Node) { + /** + * Visits a node, saving and restoring state variables on the stack. + * + * @param node The node to visit. + */ + function visitWithStack(node: Node, visitor: (node: Node) => Node): Node { + const savedCurrentNamespace = currentNamespace; + const savedCurrentScope = currentScope; + const savedCurrentParent = currentParent; + const savedCurrentNode = currentNode; + const savedIsRightmostExpression = isRightmostExpression; + onBeforeVisitNode(node); + node = visitor(node); + currentNamespace = savedCurrentNamespace; + currentScope = savedCurrentScope; + currentParent = savedCurrentParent; + currentNode = savedCurrentNode; + isRightmostExpression = savedIsRightmostExpression; + return node; + } + + /** + * General-purpose node visitor. + * + * @param node The node to visit. + */ + function visitor(node: Node): Node { + return visitWithStack(node, visitorWorker); + } + + /** + * Visits and possibly transforms any node. + * + * @param node The node to visit. + */ + function visitorWorker(node: Node): Node { if (node.transformFlags & TransformFlags.TypeScript) { - return visitorWorker(node); + // This node is explicitly marked as TypeScript, so we should transform the node. + node = visitTypeScript(node); } else if (node.transformFlags & TransformFlags.ContainsTypeScript) { - return visitEachChild(node, visitor, context); + // This node contains TypeScript, so we should visit its children. + node = visitEachChild(node, visitor, context); } - else { - return node; + + return node; + } + + /** + * Specialized visitor that visits the immediate children of a namespace. + * + * @param node The node to visit. + */ + function namespaceElementVisitor(node: Node): Node { + return visitWithStack(node, namespaceElementVisitorWorker); + } + + /** + * Specialized visitor that visits the immediate children of a namespace. + * + * @param node The node to visit. + */ + function namespaceElementVisitorWorker(node: Node): Node { + if (node.transformFlags & TransformFlags.TypeScript + || node.flags & NodeFlags.Export) { + // This node is explicitly marked as TypeScript, or is exported at the namespace + // level, so we should transform the node. + node = visitTypeScript(node); + } + else if (node.transformFlags & TransformFlags.ContainsTypeScript) { + // This node contains TypeScript, so we should visit its children. + node = visitEachChild(node, visitor, context); + } + + return node; + } + + /** + * Specialized visitor that visits the immediate children of a class with TypeScript syntax. + * + * @param node The node to visit. + */ + function classElementVisitor(node: Node) { + return visitWithStack(node, classElementVisitorWorker); + } + + /** + * Specialized visitor that visits the immediate children of a class with TypeScript syntax. + * + * @param node The node to visit. + */ + function classElementVisitorWorker(node: Node) { + if (node.kind === SyntaxKind.Constructor) { + // TypeScript constructors are elided. + return undefined; + } + + return visitorWorker(node); + } + + function visitTypeScript(node: Node): Node { + // TypeScript ambient declarations are elided. + if (node.flags & NodeFlags.Ambient) { + return; + } + + switch (node.kind) { + case SyntaxKind.ExportKeyword: + case SyntaxKind.DefaultKeyword: + // ES6 export and default modifiers are elided when inside a namespace. + return currentNamespace ? undefined : node; + + case SyntaxKind.PublicKeyword: + case SyntaxKind.PrivateKeyword: + case SyntaxKind.ProtectedKeyword: + case SyntaxKind.AbstractKeyword: + case SyntaxKind.AsyncKeyword: + case SyntaxKind.ConstKeyword: + case SyntaxKind.DeclareKeyword: + // TypeScript accessibility modifiers are elided. + + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + case SyntaxKind.TypeLiteral: + case SyntaxKind.TypePredicate: + case SyntaxKind.TypeParameter: + case SyntaxKind.AnyKeyword: + case SyntaxKind.BooleanKeyword: + case SyntaxKind.StringKeyword: + case SyntaxKind.NumberKeyword: + case SyntaxKind.VoidKeyword: + case SyntaxKind.ConstructorType: + case SyntaxKind.FunctionType: + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeReference: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.StringLiteralType: + case SyntaxKind.ThisType: + // TypeScript type nodes are elided. + + case SyntaxKind.IndexSignature: + // TypeScript index signatures are elided. + + case SyntaxKind.Decorator: + // TypeScript decorators are elided. They will be emitted as part of transformClassDeclaration. + + case SyntaxKind.InterfaceDeclaration: + case SyntaxKind.TypeAliasDeclaration: + // TypeScript type-only declarations are elided + + case SyntaxKind.PropertyDeclaration: + // TypeScript property declarations are elided. + + case SyntaxKind.Constructor: + // TypeScript constructors are elided. The constructor of a class will be + // reordered to the start of the member list in `transformClassDeclaration`. + return undefined; + + case SyntaxKind.ClassDeclaration: + // This is a class declaration with TypeScript syntax extensions. + // + // TypeScript class syntax extensions include: + // - decorators + // - optional `implements` heritage clause + // - parameter property assignments in the constructor + // - property declarations + // - index signatures + // - method overload signatures + // - async methods + return visitClassDeclaration(node); + + case SyntaxKind.ClassExpression: + // This is a class expression with TypeScript syntax extensions. + // + // TypeScript class syntax extensions include: + // - decorators + // - optional `implements` heritage clause + // - parameter property assignments in the constructor + // - property declarations + // - index signatures + // - method overload signatures + // - async methods + return visitClassExpression(node); + + case SyntaxKind.HeritageClause: + // This is a heritage clause with TypeScript syntax extensions. + // + // TypeScript heritage clause extensions include: + // - `implements` clause + return visitHeritageClause(node); + + case SyntaxKind.ExpressionWithTypeArguments: + // TypeScript supports type arguments on an expression in an `extends` heritage clause. + return visitExpressionWithTypeArguments(node); + + case SyntaxKind.MethodDeclaration: + // TypeScript method declarations may be 'async', and may have decorators, modifiers + // or type annotations. + return visitMethodDeclaration(node); + + case SyntaxKind.GetAccessor: + // Get Accessors can have TypeScript modifiers, decorators, and type annotations. + return visitGetAccessor(node); + + case SyntaxKind.SetAccessor: + // Set Accessors can have TypeScript modifiers, decorators, and type annotations. + return visitSetAccessor(node); + + case SyntaxKind.FunctionDeclaration: + // TypeScript function declarations may be 'async' + return visitFunctionDeclaration(node); + + case SyntaxKind.FunctionExpression: + // TypeScript function expressions may be 'async' + return visitFunctionExpression(node); + + case SyntaxKind.ArrowFunction: + // TypeScript arrow functions may be 'async' + return visitArrowFunction(node); + + case SyntaxKind.Parameter: + // This is a parameter declaration with TypeScript syntax extensions. + // + // TypeScript parameter declaration syntax extensions include: + // - decorators + // - accessibility modifiers + // - the question mark (?) token for optional parameters + // - type annotations + return visitParameter(node); + + case SyntaxKind.ParenthesizedExpression: + // ParenthesizedExpressions are TypeScript if their expression is a + // TypeAssertion or AsExpression + return visitParenthesizedExpression(node); + + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.AsExpression: + // TypeScript type assertions are removed, but their subtrees are preserved. + return visitNode((node).expression, visitor, isExpression); + + case SyntaxKind.EnumDeclaration: + // TypeScript enum declarations do not exist in ES6 and must be rewritten. + return visitEnumDeclaration(node); + + case SyntaxKind.AwaitExpression: + // TypeScript 'await' expressions must be transformed. + return visitAwaitExpression(node); + + case SyntaxKind.VariableStatement: + // TypeScript namespace exports for variable statements must be transformed. + return visitVariableStatement(node); + + case SyntaxKind.ModuleDeclaration: + // TypeScript namespace declarations must be transformed. + return visitModuleDeclaration(node); + + case SyntaxKind.ImportEqualsDeclaration: + // TypeScript namespace or external module import. + return visitImportEqualsDeclaration(node); + + default: + Debug.fail("Unexpected node."); + break; } } - function visitorWorker(node: Node): Node { + /** + * Performs actions that should always occur immediately before visiting a node. + * + * @param node The node to visit. + */ + function onBeforeVisitNode(node: Node) { + currentParent = currentNode; + currentNode = node; + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.CaseBlock: + case SyntaxKind.ModuleBlock: + case SyntaxKind.Block: + currentScope = node; + break; + } + + // Keep track of whether this is the right-most expression of an expression tree. + // This is used to determine whether to parenthesize an `await` expression transformed + // into a `yield` expression. + isRightmostExpression = currentParent && isRightmost(currentParent, currentNode, isRightmostExpression); + } + + /** + * Transforms a class declaration with TypeScript syntax into compatible ES6. + * + * This function will only be called when one of the following conditions are met: + * - The class has decorators. + * - The class has property declarations with initializers. + * - The class contains a constructor that contains parameters with accessibility modifiers. + * - The class is an export in a TypeScript namespace. + * + * @param node The node to transform. + */ + function visitClassDeclaration(node: ClassDeclaration): NodeArrayNode { + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const statements: Statement[] = []; + const modifiers = visitNodes(node.modifiers, visitor, isModifier); + const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); + const members = transformClassMembers(node, heritageClauses !== undefined); + let decoratedClassAlias: Identifier; + + // emit name if + // - node has a name + // - node has static initializers + // + let name = node.name; + if (!name && staticProperties.length > 0) { + name = getGeneratedNameForNode(node); + } + + if (node.decorators) { + // When we emit an ES6 class that has a class decorator, we must tailor the + // emit to certain specific cases. + // + // In the simplest case, we emit the class declaration as a let declaration, and + // evaluate decorators after the close of the class body: + // + // [Example 1] + // --------------------------------------------------------------------- + // TypeScript | Javascript + // --------------------------------------------------------------------- + // @dec | let C = class C { + // class C { | } + // } | C = __decorate([dec], C); + // --------------------------------------------------------------------- + // @dec | let C = class C { + // export class C { | } + // } | C = __decorate([dec], C); + // | export { C }; + // --------------------------------------------------------------------- + // + // If a class declaration contains a reference to itself *inside* of the class body, + // this introduces two bindings to the class: One outside of the class body, and one + // inside of the class body. If we apply decorators as in [Example 1] above, there + // is the possibility that the decorator `dec` will return a new value for the + // constructor, which would result in the binding inside of the class no longer + // pointing to the same reference as the binding outside of the class. + // + // As a result, we must instead rewrite all references to the class *inside* of the + // class body to instead point to a local temporary alias for the class: + // + // [Example 2] + // --------------------------------------------------------------------- + // TypeScript | Javascript + // --------------------------------------------------------------------- + // @dec | let C_1; + // class C { | let C = C_1 = class C { + // static x() { return C.y; } | static x() { return C_1.y; } + // static y = 1; | } + // } | C.y = 1; + // | C = C_1 = __decorate([dec], C); + // --------------------------------------------------------------------- + // @dec | let C_1; + // export class C { | let C = C_1 = class C { + // static x() { return C.y; } | static x() { return C_1.y; } + // static y = 1; | } + // } | C.y = 1; + // | C = C_1 = __decorate([dec], C); + // | export { C }; + // --------------------------------------------------------------------- + // + // If a class declaration is the default export of a module, we instead emit + // the export after the decorated declaration: + // + // [Example 3] + // --------------------------------------------------------------------- + // TypeScript | Javascript + // --------------------------------------------------------------------- + // @dec | let default_1 = class { + // export default class { | } + // } | default_1 = __decorate([dec], default_1); + // | export default default_1; + // --------------------------------------------------------------------- + // @dec | let C = class C { + // export default class C { | } + // } | C = __decorate([dec], C); + // | export default C; + // --------------------------------------------------------------------- + // + // If the class declaration is the default export and a reference to itself + // inside of the class body, we must emit both an alias for the class *and* + // move the export after the declaration: + // + // [Example 4] + // --------------------------------------------------------------------- + // TypeScript | Javascript + // --------------------------------------------------------------------- + // @dec | let C_1; + // export default class C { | let C = C_1 = class C { + // static x() { return C.y; } | static x() { return C_1.y; } + // static y = 1; | } + // } | C.y = 1; + // | C = C_1 = __decorate([dec], C); + // | export default C; + // --------------------------------------------------------------------- + // + + // class ${name} ${heritageClauses} { + // ${members} + // } + let classExpression: Expression = setOriginalNode( + createClassExpression( + name, + heritageClauses, + members, + /*location*/ node + ), + node + ); + + // Record an alias to avoid class double-binding. + if (resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.ClassWithBodyScopedClassBinding) { + decoratedClassAlias = makeUniqueName(node.name ? node.name.text : "default"); + decoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAlias; + + // We emit the class alias as a `let` declaration here so that it has the same + // TDZ as the class. + // let ${decoratedClassAlias}; + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration(decoratedClassAlias) + ], + /*location*/ undefined, + NodeFlags.Let) + ) + ); + + // ${decoratedClassAlias} = ${classExpression} + classExpression = createAssignment( + cloneNode(decoratedClassAlias), + classExpression, + /*location*/ node); + } + + // let ${name} = ${classExpression}; + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration( + name, + classExpression + ) + ], + /*location*/ undefined, + NodeFlags.Let) + ) + ); + } + else { + // ${modifiers} class ${name} ${heritageClauses} { + // ${members} + // } + addNode(statements, + setOriginalNode( + createClassDeclaration( + modifiers, + name, + heritageClauses, + members, + /*location*/ node + ), + node + ) + ); + } + + // Emit static property assignment. Because classDeclaration is lexically evaluated, + // it is safe to emit static property assignment after classDeclaration + // From ES6 specification: + // HasLexicalDeclaration (N) : Determines if the argument identifier has a binding in this environment record that was created using + // a lexical declaration such as a LexicalDeclaration or a ClassDeclaration. + addNodes(statements, generateInitializedPropertyStatements(node, staticProperties, name)); + + // Write any decorators of the node. + addNodes(statements, generateClassElementDecorationStatements(node, /*isStatic*/ false)); + addNodes(statements, generateClassElementDecorationStatements(node, /*isStatic*/ true)); + addNode(statements, generateConstructorDecorationStatement(node, decoratedClassAlias)) + + // If the class is exported as part of a TypeScript namespace, emit the namespace export. + // Otherwise, if the class was exported at the top level and was decorated, emit an export + // declaration or export default for the class. + if (isNamespaceExport(node)) { + addNode(statements, createNamespaceExport(name, name)); + } + else if (node.decorators) { + if (isDefaultExternalModuleExport(node)) { + addNode(statements, createExportDefault(name)); + } + else if (isNamedExternalModuleExport(node)) { + addNode(statements, createModuleExport(name)); + } + } + + return createNodeArrayNode(statements); + } + + /** + * Transforms a class expression with TypeScript syntax into compatible ES6. + * + * This function will only be called when one of the following conditions are met: + * - The class has property declarations with initializers. + * - The class contains a constructor that contains parameters with accessibility modifiers. + * + * @param node The node to transform. + */ + function visitClassExpression(node: ClassExpression): LeftHandSideExpression { + const staticProperties = getInitializedProperties(node, /*isStatic*/ true); + const heritageClauses = visitNodes(node.heritageClauses, visitor, isHeritageClause); + const members = transformClassMembers(node, heritageClauses !== undefined); + + // emit name if + // - node has a name + // - node has static initializers + // + let name = node.name; + if (!name && staticProperties.length > 0) { + name = getGeneratedNameForNode(node); + } + + const classExpression = setOriginalNode( + createClassExpression( + name, + heritageClauses, + members, + /*location*/ node + ), + node + ); + + if (staticProperties.length > 0) { + const expressions: Expression[] = []; + const temp = createTempVariable(); + hoistVariableDeclaration(temp); + addNode(expressions, createAssignment(temp, classExpression)); + addNodes(expressions, generateInitializedPropertyExpressions(node, staticProperties, temp)); + addNode(expressions, temp); + return createParen(inlineExpressions(expressions)); + } + + return classExpression; + } + + /** + * Transforms the members of a class. + * + * @param node The current class. + * @param hasExtendsClause A value indicating whether the class has an extends clause. + */ + function transformClassMembers(node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean) { + const members: ClassElement[] = []; + addNode(members, transformConstructor(node, hasExtendsClause)); + addNodes(members, visitNodes(node.members, classElementVisitor, isClassElement)); + return members; + } + + /** + * Transforms (or creates) a constructor for a class. + * + * @param node The current class. + * @param hasExtendsClause A value indicating whether the class has an extends clause. + */ + function transformConstructor(node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean) { + // Check if we have property assignment inside class declaration. + // If there is a property assignment, we need to emit constructor whether users define it or not + // If there is no property assignment, we can omit constructor if users do not define it + const hasInstancePropertyWithInitializer = forEach(node.members, isInstanceInitializedProperty); + const hasParameterPropertyAssignments = node.transformFlags & TransformFlags.ContainsParameterPropertyAssignments; + const constructor = getFirstConstructorWithBody(node); + + // If the class does not contain nodes that require a synthesized constructor, + // accept the current constructor if it exists. + if (!hasInstancePropertyWithInitializer && !hasParameterPropertyAssignments) { + return visitEachChild(constructor, visitor, context); + } + + const parameters = transformConstructorParameters(constructor, hasExtendsClause); + const body = transformConstructorBody(node, constructor, hasExtendsClause, parameters); + + // constructor(${parameters}) { + // ${body} + // } + return startOnNewLine( + setOriginalNode( + createConstructor( + parameters, + body, + /*location*/ constructor + ), + constructor + ) + ); + } + + /** + * Transforms (or creates) the parameters for the constructor of a class with + * parameter property assignments or instance property initializers. + * + * @param constructor The constructor declaration. + * @param hasExtendsClause A value indicating whether the class has an extends clause. + */ + function transformConstructorParameters(constructor: ConstructorDeclaration, hasExtendsClause: boolean) { + return constructor + ? visitNodes(constructor.parameters, visitor, isParameter) + : hasExtendsClause ? [createRestParameter(makeUniqueName("args"))] : []; + } + + /** + * Transforms (or creates) a constructor body for a class with parameter property + * assignments or instance property initializers. + * + * @param node The current class. + * @param constructor The current class constructor. + * @param hasExtendsClause A value indicating whether the class has an extends clause. + * @param parameters The transformed parameters for the constructor. + */ + function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration, hasExtendsClause: boolean, parameters: ParameterDeclaration[]) { + let hasSuperCall = false; + const statements: Statement[] = []; + + // The body of a constructor is a new lexical environment + startLexicalEnvironment(); + + if (constructor) { + const superCall = visitNode(findInitialSuperCall(constructor), visitor, isStatement); + if (superCall) { + // Adds the existing super call as the first line of the constructor. + addNode(statements, superCall); + hasSuperCall = true; + } + + // Add parameters with property assignments. Transforms this: + // + // constructor (public x, public y) { + // } + // + // Into this: + // + // constructor (x, y) { + // this.x = x; + // this.y = y; + // } + // + const propertyAssignments = getParametersWithPropertyAssignments(constructor); + addNodes(statements, map(propertyAssignments, transformParameterWithPropertyAssignment)); + } + else if (hasExtendsClause) { + Debug.assert(parameters.length === 1 && isIdentifier(parameters[0].name)); + + // Add a synthetic `super` call: + // + // super(...args); + // + addNode(statements, + createStatement( + createCall( + createSuper(), + [createSpread(parameters[0].name)] + ) + ) + ); + } + + // Add the property initializers. Transforms this: + // + // public x = 1; + // + // Into this: + // + // constructor() { + // this.x = 1; + // } + // + const properties = getInitializedProperties(node, /*isStatic*/ false); + addNodes(statements, generateInitializedPropertyStatements(node, properties, createThis())); + + if (constructor) { + // The class already had a constructor, so we should add the existing statements, skipping the initial super call. + addNodes(statements, visitNodes(constructor.body.statements, visitor, isStatement, hasSuperCall ? 1 : 0)); + } + + // End the lexical environment. + addNodes(statements, endLexicalEnvironment()); + return createBlock(statements); + } + + /** + * Finds the initial super-call for a constructor. + * + * @param ctor The constructor node. + */ + function findInitialSuperCall(ctor: ConstructorDeclaration): ExpressionStatement { + if (ctor.body) { + const statements = ctor.body.statements; + const statement = statements.length ? statements[0] : undefined; + if (statement.kind === SyntaxKind.ExpressionStatement) { + const expression = (statement).expression; + if (expression.kind === SyntaxKind.CallExpression) { + if ((expression).expression.kind === SyntaxKind.SuperKeyword) { + return statement; + } + } + } + } + + return undefined; + } + + /** + * Gets all parameters of a constructor that should be transformed into property assignments. + * + * @param node The constructor node. + */ + function getParametersWithPropertyAssignments(node: ConstructorDeclaration): ParameterDeclaration[] { + return filter(node.parameters, isParameterWithPropertyAssignment); + } + + /** + * Determines whether a parameter should be transformed into a property assignment. + * + * @param parameter The parameter node. + */ + function isParameterWithPropertyAssignment(parameter: ParameterDeclaration) { + return parameter.flags & NodeFlags.AccessibilityModifier + && isIdentifier(parameter.name); + } + + /** + * Transforms a parameter into a property assignment statement. + * + * @param node The parameter declaration. + */ + function transformParameterWithPropertyAssignment(node: ParameterDeclaration) { + Debug.assert(isIdentifier(node.name)); + + const name = cloneNode(node.name); + return startOnNewLine( + createStatement( + createAssignment( + createPropertyAccess(createThis(), name), + name + ) + ) + ); + } + + /** + * Gets all property declarations with initializers on either the static or instance side of a class. + * + * @param node The class node. + * @param isStatic A value indicating whether to get properties from the static or instance side of the class. + */ + function getInitializedProperties(node: ClassExpression | ClassDeclaration, isStatic: boolean): PropertyDeclaration[] { + return filter(node.members, isStatic ? isStaticInitializedProperty : isInstanceInitializedProperty); + } + + /** + * Gets a value indicating whether a class element is a static property declaration with an initializer. + * + * @param member The class element node. + */ + function isStaticInitializedProperty(member: ClassElement): member is PropertyDeclaration { + return isInitializedProperty(member, /*isStatic*/ true); + } + + /** + * Gets a value indicating whether a class element is an instance property declaration with an initializer. + * + * @param member The class element node. + */ + function isInstanceInitializedProperty(member: ClassElement): member is PropertyDeclaration { + return isInitializedProperty(member, /*isStatic*/ false); + } + + /** + * Gets a value indicating whether a class element is either a static or an instance property declaration with an initializer. + * + * @param member The class element node. + * @param isStatic A value indicating whether the member should be a static or instance member. + */ + function isInitializedProperty(member: ClassElement, isStatic: boolean) { + return member.kind === SyntaxKind.PropertyDeclaration + && isStatic === ((member.flags & NodeFlags.Static) !== 0) + && (member).initializer !== undefined; + } + + /** + * Generates assignment statements for property initializers. + * + * @param node The class node. + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyStatements(node: ClassExpression | ClassDeclaration, properties: PropertyDeclaration[], receiver: LeftHandSideExpression) { + return map(generateInitializedPropertyExpressions(node, properties, receiver), expressionToStatement); + } + + /** + * Generates assignment expressions for property initializers. + * + * @param node The class node. + * @param properties An array of property declarations to transform. + * @param receiver The receiver on which each property should be assigned. + */ + function generateInitializedPropertyExpressions(node: ClassExpression | ClassDeclaration, properties: PropertyDeclaration[], receiver: LeftHandSideExpression) { + const expressions: Expression[] = []; + for (const property of properties) { + expressions.push(transformInitializedProperty(node, property, receiver)); + } + return expressions; + } + + /** + * Transforms a property initializer into an assignment statement. + * + * @param node The class containing the property. + * @param property The property declaration. + * @param receiver The object receiving the property assignment. + */ + function transformInitializedProperty(node: ClassExpression | ClassDeclaration, property: PropertyDeclaration, receiver: LeftHandSideExpression) { + const propertyName = visitPropertyNameOfClassElement(property); + const initializer = visitNode(property.initializer, visitor, isExpression); + return createAssignment( + createMemberAccessForPropertyName(receiver, propertyName), + initializer + ); + } + + /** + * Gets either the static or instance members of a class that are decorated, or have + * parameters that are decorated. + * + * @param node The class containing the member. + * @param isStatic A value indicating whether to retrieve static or instance members of + * the class. + */ + function getDecoratedClassElements(node: ClassExpression | ClassDeclaration, isStatic: boolean): ClassElement[] { + return filter(node.members, isStatic ? isStaticDecoratedClassElement : isInstanceDecoratedClassElement); + } + + /** + * Determines whether a class member is a static member of a class that is decorated, or + * has parameters that are decorated. + * + * @param member The class member. + */ + function isStaticDecoratedClassElement(member: ClassElement) { + return isDecoratedClassElement(member, /*isStatic*/ true); + } + + /** + * Determines whether a class member is an instance member of a class that is decorated, + * or has parameters that are decorated. + * + * @param member The class member. + */ + function isInstanceDecoratedClassElement(member: ClassElement) { + return isDecoratedClassElement(member, /*isStatic*/ false); + } + + /** + * Determines whether a class member is either a static or an instance member of a class + * that is decorated, or has parameters that are decorated. + * + * @param member The class member. + */ + function isDecoratedClassElement(member: ClassElement, isStatic: boolean) { + return nodeOrChildIsDecorated(member) + && isStatic === ((member.flags & NodeFlags.Static) !== 0); + } + + /** + * A structure describing the decorators for a class element. + */ + interface AllDecorators { + decorators: Decorator[]; + parameters?: Decorator[][]; + } + + /** + * Gets an array of arrays of decorators for the parameters of a function-like node. + * The offset into the result array should correspond to the offset of the parameter. + * + * @param node The function-like node. + */ + function getDecoratorsOfParameters(node: FunctionLikeDeclaration) { + let decorators: Decorator[][]; + if (node) { + const parameters = node.parameters; + for (let i = 0; i < parameters.length; i++) { + const parameter = parameters[i]; + if (decorators || parameter.decorators) { + if (!decorators) { + decorators = new Array(parameters.length); + } + + decorators[i] = parameter.decorators; + } + } + } + + return decorators; + } + + /** + * Gets an AllDecorators object containing the decorators for the class and the decorators for the + * parameters of the constructor of the class. + * + * @param node The class node. + */ + function getAllDecoratorsOfConstructor(node: ClassExpression | ClassDeclaration): AllDecorators { + const decorators = node.decorators; + const parameters = getDecoratorsOfParameters(getFirstConstructorWithBody(node)); + if (!decorators && !parameters) { + return undefined; + } + + return { + decorators, + parameters + }; + } + + /** + * Gets an AllDecorators object containing the decorators for the member and its parameters. + * + * @param node The class node that contains the member. + * @param member The class member. + */ + function getAllDecoratorsOfClassElement(node: ClassExpression | ClassDeclaration, member: ClassElement): AllDecorators { + switch (member.kind) { + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return getAllDecoratorsOfAccessors(node, member); + + case SyntaxKind.MethodDeclaration: + return getAllDecoratorsOfMethod(member); + + case SyntaxKind.PropertyDeclaration: + return getAllDecoratorsOfProperty(member); + + default: + return undefined; + } + } + + /** + * Gets an AllDecorators object containing the decorators for the accessor and its parameters. + * + * @param node The class node that contains the accessor. + * @param accessor The class accessor member. + */ + function getAllDecoratorsOfAccessors(node: ClassExpression | ClassDeclaration, accessor: AccessorDeclaration): AllDecorators { + if (!accessor.body) { + return undefined; + } + + const { firstAccessor, secondAccessor, setAccessor } = getAllAccessorDeclarations(node.members, accessor); + if (accessor !== firstAccessor) { + return undefined; + } + + const decorators = firstAccessor.decorators || (secondAccessor && secondAccessor.decorators); + const parameters = getDecoratorsOfParameters(setAccessor); + if (!decorators && !parameters) { + return undefined; + } + + return { decorators, parameters }; + } + + /** + * Gets an AllDecorators object containing the decorators for the method and its parameters. + * + * @param method The class method member. + */ + function getAllDecoratorsOfMethod(method: MethodDeclaration): AllDecorators { + if (!method.body) { + return undefined; + } + + const decorators = method.decorators; + const parameters = getDecoratorsOfParameters(method); + if (!decorators && !parameters) { + return undefined; + } + + return { decorators, parameters }; + } + + /** + * Gets an AllDecorators object containing the decorators for the property. + * + * @param property The class property member. + */ + function getAllDecoratorsOfProperty(property: PropertyDeclaration): AllDecorators { + const decorators = property.decorators; + if (!decorators) { + return undefined; + + } + + return { decorators }; + } + + /** + * Transforms all of the decorators for a declaration into an array of expressions. + * + * @param node The declaration node. + * @param allDecorators The AllDecorators object for the node. + */ + function transformAllDecoratorsOfDeclaration(node: Declaration, allDecorators: AllDecorators) { + if (!allDecorators) { + return undefined; + } + + const decoratorExpressions: Expression[] = []; + addNodes(decoratorExpressions, map(allDecorators.decorators, transformDecorator)); + addNodes(decoratorExpressions, flatMap(allDecorators.parameters, transformDecoratorsOfParameter)); + addTypeMetadata(node, decoratorExpressions); + return decoratorExpressions; + } + + /** + * Generates statements used to apply decorators to either the static or instance members + * of a class. + * + * @param node The class node. + * @param isStatic A value indicating whether to generate statements for static or + * instance members. + */ + function generateClassElementDecorationStatements(node: ClassDeclaration, isStatic: boolean) { + return map(generateClassElementDecorationExpressions(node, isStatic), expressionToStatement); + } + + /** + * Generates expressions used to apply decorators to either the static or instance members + * of a class. + * + * @param node The class node. + * @param isStatic A value indicating whether to generate expressions for static or + * instance members. + */ + function generateClassElementDecorationExpressions(node: ClassExpression | ClassDeclaration, isStatic: boolean) { + const members = getDecoratedClassElements(node, isStatic); + let expressions: Expression[]; + for (const member of members) { + const expression = generateClassElementDecorationExpression(node, member); + if (expression) { + if (!expressions) { + expressions = [expression]; + } + else { + expressions.push(expression); + } + } + } + return expressions; + } + + /** + * Generates an expression used to evaluate class element decorators at runtime. + * + * @param node The class node that contains the member. + * @param member The class member. + */ + function generateClassElementDecorationExpression(node: ClassExpression | ClassDeclaration, member: ClassElement) { + const allDecorators = getAllDecoratorsOfClassElement(node, member); + const decoratorExpressions = transformAllDecoratorsOfDeclaration(member, allDecorators); + if (!decoratorExpressions) { + return undefined; + } + + // Emit the call to __decorate. Given the following: + // + // class C { + // @dec method(@dec2 x) {} + // @dec get accessor() {} + // @dec prop; + // } + // + // The emit for a method is: + // + // __decorate([ + // dec, + // __param(0, dec2), + // __metadata("design:type", Function), + // __metadata("design:paramtypes", [Object]), + // __metadata("design:returntype", void 0) + // ], C.prototype, "method", undefined); + // + // The emit for an accessor is: + // + // __decorate([ + // dec + // ], C.prototype, "accessor", undefined); + // + // The emit for a property is: + // + // __decorate([ + // dec + // ], C.prototype, "prop"); + // + + const prefix = getClassMemberPrefix(node, member); + const memberName = getExpressionForPropertyName(member); + const descriptor = languageVersion > ScriptTarget.ES3 + ? member.kind === SyntaxKind.PropertyDeclaration + // We emit `void 0` here to indicate to `__decorate` that it can invoke `Object.defineProperty` directly, but that it + // should not invoke `Object.getOwnPropertyDescriptor`. + ? createVoidZero() + + // We emit `null` here to indicate to `__decorate` that it can invoke `Object.getOwnPropertyDescriptor` directly. + // We have this extra argument here so that we can inject an explicit property descriptor at a later date. + : createNull() + : undefined; + + return createDecorateHelper( + decoratorExpressions, + prefix, + memberName, + descriptor + ); + } + + /** + * Generates a __decorate helper call for a class constructor. + * + * @param node The class node. + */ + function generateConstructorDecorationStatement(node: ClassDeclaration, decoratedClassAlias: Identifier) { + const expression = generateConstructorDecorationExpression(node, decoratedClassAlias); + return expression ? createStatement(expression) : undefined; + } + + /** + * Generates a __decorate helper call for a class constructor. + * + * @param node The class node. + */ + function generateConstructorDecorationExpression(node: ClassExpression | ClassDeclaration, decoratedClassAlias: Identifier) { + const allDecorators = getAllDecoratorsOfConstructor(node); + const decoratorExpressions = transformAllDecoratorsOfDeclaration(node, allDecorators); + if (!decoratorExpressions) { + return undefined; + } + + // Emit the call to __decorate. Given the class: + // + // @dec + // class C { + // } + // + // The emit for the class is: + // + // C = __decorate([dec], C); + // + + const expression = createAssignment( + getDeclarationName(node), + createDecorateHelper( + decoratorExpressions, + getDeclarationName(node) + ) + ); + + return decoratedClassAlias + ? createAssignment(decoratedClassAlias, expression) + : expression; + } + + /** + * Transforms a decorator into an expression. + * + * @param decorator The decorator node. + */ + function transformDecorator(decorator: Decorator) { + return visitNode(decorator.expression, visitor, isExpression); + } + + /** + * Transforms the decorators of a parameter. + * + * @param decorators The decorators for the parameter at the provided offset. + * @param parameterOffset The offset of the parameter. + */ + function transformDecoratorsOfParameter(decorators: Decorator[], parameterOffset: number) { + let expressions: Expression[]; + if (decorators) { + expressions = []; + for (const decorator of decorators) { + expressions.push(createParamHelper(transformDecorator(decorator), parameterOffset)); + } + } + + return expressions; + } + + /** + * Adds optional type metadata for a declaration. + * + * @param node The declaration node. + * @param decoratorExpressions The destination array to which to add new decorator expressions. + */ + function addTypeMetadata(node: Declaration, decoratorExpressions: Expression[]) { + if (compilerOptions.emitDecoratorMetadata) { + if (shouldAppendTypeMetadata(node)) { + decoratorExpressions.push(createMetadataHelper("design:type", serializeTypeOfNode(node), /*defer*/ true)); + } + if (shouldAppendParamTypesMetadata(node)) { + decoratorExpressions.push(createMetadataHelper("design:paramtypes", serializeParameterTypesOfNode(node), /*defer*/ true)); + } + if (shouldAppendReturnTypeMetadata(node)) { + decoratorExpressions.push(createMetadataHelper("design:returntype", serializeReturnTypeOfNode(node), /*defer*/ true)); + } + } + } + + /** + * Determines whether to emit the "design:type" metadata based on the node's kind. + * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata + * compiler option is set. + * + * @param node The node to test. + */ + function shouldAppendTypeMetadata(node: Declaration): boolean { + const kind = node.kind; + return kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor + || kind === SyntaxKind.PropertyDeclaration; + } + + /** + * Determines whether to emit the "design:returntype" metadata based on the node's kind. + * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata + * compiler option is set. + * + * @param node The node to test. + */ + function shouldAppendReturnTypeMetadata(node: Declaration): boolean { + return node.kind === SyntaxKind.MethodDeclaration; + } + + /** + * Determines whether to emit the "design:paramtypes" metadata based on the node's kind. + * The caller should have already tested whether the node has decorators and whether the emitDecoratorMetadata + * compiler option is set. + * + * @param node The node to test. + */ + function shouldAppendParamTypesMetadata(node: Declaration): boolean { + const kind = node.kind; + return kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.ClassExpression + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor; + } + + /** + * Serializes the type of a node for use with decorator type metadata. + * + * @param node The node that should have its type serialized. + */ + function serializeTypeOfNode(node: Node): Expression { + switch (node.kind) { + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.GetAccessor: + return serializeTypeNode((node).type); + case SyntaxKind.SetAccessor: + return serializeTypeNode(getSetAccessorTypeAnnotationNode(node)); + case SyntaxKind.ClassDeclaration: + case SyntaxKind.ClassExpression: + case SyntaxKind.MethodDeclaration: + return createIdentifier("Function"); + default: + return createVoidZero(); + } + } + + /** + * Gets the most likely element type for a TypeNode. This is not an exhaustive test + * as it assumes a rest argument can only be an array type (either T[], or Array). + * + * @param node The type node. + */ + function getRestParameterElementType(node: TypeNode) { + if (node.kind === SyntaxKind.ArrayType) { + return (node).elementType; + } + else if (node.kind === SyntaxKind.TypeReference) { + return singleOrUndefined((node).typeArguments); + } + else { + return undefined; + } + } + + /** + * Serializes the types of the parameters of a node for use with decorator type metadata. + * + * @param node The node that should have its parameter types serialized. + */ + function serializeParameterTypesOfNode(node: Node): Expression { + const valueDeclaration = + isClassLike(node) + ? getFirstConstructorWithBody(node) + : isFunctionLike(node) && nodeIsPresent(node.body) + ? node + : undefined; + + const expressions: Expression[] = []; + if (valueDeclaration) { + for (const parameter of valueDeclaration.parameters) { + if (parameter.dotDotDotToken) { + expressions.push(serializeTypeNode(getRestParameterElementType(parameter.type))); + } + else { + expressions.push(serializeTypeOfNode(parameter)); + } + } + } + + return createArrayLiteral(expressions); + } + + /** + * Serializes the return type of a node for use with decorator type metadata. + * + * @param node The node that should have its return type serialized. + */ + function serializeReturnTypeOfNode(node: Node): Expression { + if (isFunctionLike(node)) { + return serializeTypeNode(node.type); + } + + return undefined; + } + + /** + * Serializes a type node for use with decorator type metadata. + * + * Types are serialized in the following fashion: + * - Void types point to "undefined" (e.g. "void 0") + * - Function and Constructor types point to the global "Function" constructor. + * - Interface types with a call or construct signature types point to the global + * "Function" constructor. + * - Array and Tuple types point to the global "Array" constructor. + * - Type predicates and booleans point to the global "Boolean" constructor. + * - String literal types and strings point to the global "String" constructor. + * - Enum and number types point to the global "Number" constructor. + * - Symbol types point to the global "Symbol" constructor. + * - Type references to classes (or class-like variables) point to the constructor for the class. + * - Anything else points to the global "Object" constructor. + * + * @param node The type node to serialize. + */ + function serializeTypeNode(node: TypeNode): Expression { + if (node === undefined) { + return createIdentifier("Object"); + } + + switch (node.kind) { + case SyntaxKind.VoidKeyword: + return createVoidZero(); + + case SyntaxKind.ParenthesizedType: + return serializeTypeNode((node).type); + + case SyntaxKind.FunctionType: + case SyntaxKind.ConstructorType: + return createIdentifier("Function"); + + case SyntaxKind.ArrayType: + case SyntaxKind.TupleType: + return createIdentifier("Array"); + + case SyntaxKind.TypePredicate: + case SyntaxKind.BooleanKeyword: + return createIdentifier("Boolean") + + case SyntaxKind.StringKeyword: + case SyntaxKind.StringLiteral: + return createIdentifier("String"); + + case SyntaxKind.NumberKeyword: + return createIdentifier("Number"); + + case SyntaxKind.SymbolKeyword: + return languageVersion < ScriptTarget.ES6 + ? getGlobalSymbolNameWithFallback() + : createIdentifier("Symbol"); + + case SyntaxKind.TypeReference: + return serializeTypeReferenceNode(node); + + case SyntaxKind.TypeQuery: + case SyntaxKind.TypeLiteral: + case SyntaxKind.UnionType: + case SyntaxKind.IntersectionType: + case SyntaxKind.AnyKeyword: + break; + + default: + Debug.fail("Cannot serialize unexpected type node."); + break; + } + + return createIdentifier("Object"); + } + + /** + * Serializes a TypeReferenceNode to an appropriate JS constructor value for use with + * decorator type metadata. + * + * @param node The type reference node. + */ + function serializeTypeReferenceNode(node: TypeReferenceNode) { + // Clone the type name and parent it to a location outside of the current declaration. + const typeName = cloneEntityName(node.typeName, currentScope); + switch (resolver.getTypeReferenceSerializationKind(typeName)) { + case TypeReferenceSerializationKind.Unknown: + const serialized = serializeEntityNameAsExpression(typeName, /*useFallback*/ true); + const temp = createTempVariable(); + hoistVariableDeclaration(temp); + return createLogicalOr( + createLogicalAnd( + createStrictEquality( + createTypeOf( + createAssignment(temp, serialized) + ), + createLiteral("function") + ), + temp + ), + createIdentifier("Object") + ); + + case TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue: + return serializeEntityNameAsExpression(typeName, /*useFallback*/ false); + + case TypeReferenceSerializationKind.VoidType: + return createVoidZero(); + + case TypeReferenceSerializationKind.BooleanType: + return createIdentifier("Boolean"); + + case TypeReferenceSerializationKind.NumberLikeType: + return createIdentifier("Number"); + + case TypeReferenceSerializationKind.StringLikeType: + return createIdentifier("String"); + + case TypeReferenceSerializationKind.ArrayLikeType: + return createIdentifier("Array"); + + case TypeReferenceSerializationKind.ESSymbolType: + return languageVersion < ScriptTarget.ES6 + ? getGlobalSymbolNameWithFallback() + : createIdentifier("Symbol"); + + case TypeReferenceSerializationKind.TypeWithCallSignature: + return createIdentifier("Function"); + + case TypeReferenceSerializationKind.ObjectType: + default: + return createIdentifier("Object"); + } + } + + /** + * Serializes an entity name as an expression for decorator type metadata. + * + * @param node The entity name to serialize. + * @param useFallback A value indicating whether to use logical operators to test for the + * entity name at runtime. + */ + function serializeEntityNameAsExpression(node: EntityName, useFallback: boolean): Expression { + switch (node.kind) { + case SyntaxKind.Identifier: + if (useFallback) { + return createLogicalAnd( + createStrictInequality( + createTypeOf(node), + createLiteral("undefined") + ), + node + ); + } + + return node; + + case SyntaxKind.QualifiedName: + return serializeQualifiedNameAsExpression(node, useFallback); + } + } + + /** + * Serializes an qualified name as an expression for decorator type metadata. + * + * @param node The qualified name to serialize. + * @param useFallback A value indicating whether to use logical operators to test for the + * qualified name at runtime. + */ + function serializeQualifiedNameAsExpression(node: QualifiedName, useFallback: boolean): Expression { + let left: Expression + if (node.left.kind === SyntaxKind.Identifier) { + left = serializeEntityNameAsExpression(node.left, useFallback); + } + else if (useFallback) { + const temp = createTempVariable(); + hoistVariableDeclaration(temp); + left = createLogicalAnd( + createAssignment( + temp, + serializeEntityNameAsExpression(node.left, /*useFallback*/ true) + ), + temp + ); + } + else { + left = serializeEntityNameAsExpression(node.left, /*useFallback*/ false); + } + + return createPropertyAccess(left, node.right); + } + + /** + * Gets an expression that points to the global "Symbol" constructor at runtime if it is + * available. + */ + function getGlobalSymbolNameWithFallback(): Expression { + return createConditional( + createStrictEquality( + createTypeOf(createIdentifier("Symbol")), + createLiteral("function") + ), + createIdentifier("Symbol"), + createIdentifier("Object") + ); + } + + /** + * Gets an expression that represents a property name. For a computed property, a + * name is generated for the node. + * + * @param member The member whose name should be converted into an expression. + */ + function getExpressionForPropertyName(member: ClassElement | EnumMember): Expression { + const name = member.name; + if (isComputedPropertyName(name)) { + return getGeneratedNameForNode(name); + } + else if (isIdentifier(name)) { + return createLiteral(name.text); + } + else { + return getSynthesizedNode(name); + } + } + + /** + * Visits the property name of a class element, for use when emitting property + * initializers. For a computed property on a node with decorators, a temporary + * value is stored for later use. + * + * @param member The member whose name should be visited. + */ + function visitPropertyNameOfClassElement(member: ClassElement): PropertyName { + const name = member.name; + if (isComputedPropertyName(name)) { + let expression = visitNode(name.expression, visitor, isExpression); + if (member.decorators) { + const generatedName = getGeneratedNameForNode(name); + hoistVariableDeclaration(generatedName); + expression = createAssignment(generatedName, expression); + } + + return setOriginalNode( + createComputedPropertyName(expression, /*location*/ name), + name + ); + } + else { + return setOriginalNode( + cloneNode(name), + name + ); + } + } + + /** + * Transforms a HeritageClause with TypeScript syntax. + * + * This function will only be called when one of the following conditions are met: + * - The node is a non-`extends` heritage clause that should be elided. + * - The node is an `extends` heritage clause that should be visited, but only allow a single type. + * + * @param node The HeritageClause to transform. + */ + function visitHeritageClause(node: HeritageClause): HeritageClause { + if (node.token === SyntaxKind.ExtendsKeyword) { + const types = visitNodes(node.types, visitor, isExpressionWithTypeArguments, 0, 1); + return createHeritageClause( + SyntaxKind.ExtendsKeyword, + types, + node + ); + } + + return undefined; + } + + /** + * Transforms an ExpressionWithTypeArguments with TypeScript syntax. + * + * This function will only be called when one of the following conditions are met: + * - The node contains type arguments that should be elided. + * + * @param node The ExpressionWithTypeArguments to transform. + */ + function visitExpressionWithTypeArguments(node: ExpressionWithTypeArguments): ExpressionWithTypeArguments { + const expression = visitNode(node.expression, visitor, isLeftHandSideExpression); + return createExpressionWithTypeArguments( + expression, + node + ); + } + + /** + * Visits a method declaration of a class. + * + * This function will be called when one of the following conditions are met: + * - The node is an overload + * - The node is marked as abstract + * - The node is marked as async + * - The node has both a decorator and a computed property name + * + * @param node The method node. + */ + function visitMethodDeclaration(node: MethodDeclaration) { + if (shouldElideFunctionLikeDeclaration(node)) { + return undefined; + } + + return createMethod( + visitNodes(node.modifiers, visitor, isModifier), + visitPropertyNameOfClassElement(node), + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + node + ); + } + + /** + * Visits a get accessor declaration of a class. + * + * This function will be called when one of the following conditions are met: + * - The node is marked as abstract + * - The node has both a decorator and a computed property name + * + * @param node The get accessor node. + */ + function visitGetAccessor(node: GetAccessorDeclaration) { + if (shouldElideFunctionLikeDeclaration(node)) { + return undefined; + } + + return createGetAccessor( + visitNodes(node.modifiers, visitor, isModifier), + visitPropertyNameOfClassElement(node), + visitEachChild(node.body, visitor, context), + node + ); + } + + /** + * Visits a set accessor declaration of a class. + * + * This function will be called when one of the following conditions are met: + * - The node is marked as abstract + * - The node has both a decorator and a computed property name + * + * @param node The set accessor node. + */ + function visitSetAccessor(node: SetAccessorDeclaration) { + if (shouldElideFunctionLikeDeclaration(node)) { + return undefined; + } + + return createSetAccessor( + visitNodes(node.modifiers, visitor, isModifier), + visitPropertyNameOfClassElement(node), + visitNode(firstOrUndefined(node.parameters), visitor, isParameter), + visitEachChild(node.body, visitor, context), + node + ); + } + + /** + * Visits a function declaration. + * + * This function will be called when one of the following conditions are met: + * - The node is an overload + * - The node is marked async + * - The node is exported from a TypeScript namespace + * + * @param node The function node. + */ + function visitFunctionDeclaration(node: FunctionDeclaration): OneOrMore { + if (shouldElideFunctionLikeDeclaration(node)) { + return undefined; + } + + const func = createFunctionDeclaration( + visitNodes(node.modifiers, visitor, isModifier), + node.asteriskToken, + node.name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + node + ); + + if (isNamespaceExport(node)) { + return createNodeArrayNode([ + func, + createNamespaceExport(getSynthesizedNode(node.name), getSynthesizedNode(node.name)) + ]); + } + + return func; + } + + /** + * Visits a function expression node. + * + * This function will be called when one of the following conditions are met: + * - The node is marked async + * + * @param node The function expression node. + */ + function visitFunctionExpression(node: FunctionExpression) { + return createFunctionExpression( + node.asteriskToken, + node.name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + node + ); + } + + /** + * Determines whether a function-like declaration should be elided. A declaration should + * be elided if it is an overload, is abstract, or is an ambient declaration. + * + * @param node The declaration node. + */ + function shouldElideFunctionLikeDeclaration(node: FunctionLikeDeclaration) { + return node.body === undefined + || node.flags & (NodeFlags.Abstract | NodeFlags.Ambient); + } + + /** + * @remarks + * This function will be called when one of the following conditions are met: + * - The node is marked async + */ + function visitArrowFunction(node: ArrowFunction) { + return createArrowFunction( + visitNodes(node.parameters, visitor, isParameter), + transformConciseBody(node), + node + ); + } + + function transformFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody { + if (isAsyncFunctionLike(node)) { + return transformAsyncFunctionBody(node); + } + + return transformFunctionBodyWorker(node.body); + } + + function transformFunctionBodyWorker(body: Block) { + const savedCurrentScope = currentScope; + currentScope = body; + startLexicalEnvironment(); + const visited = visitEachChild(body, visitor, context); + const declarations = endLexicalEnvironment(); + currentScope = savedCurrentScope; + return mergeFunctionBodyLexicalEnvironment(visited, declarations, visited !== body); + } + + function transformConciseBody(node: ArrowFunction): ConciseBody { + if (isAsyncFunctionLike(node)) { + return transformAsyncFunctionBody(node); + } + + return transformConciseBodyWorker(node.body, /*forceBlockFunctionBody*/ false); + } + + function transformConciseBodyWorker(body: Block | Expression, forceBlockFunctionBody: boolean) { + if (isBlock(body)) { + return transformFunctionBodyWorker(body); + } + else { + startLexicalEnvironment(); + const visited: Expression | Block = visitNode(body, visitor, isConciseBody); + const declarations = endLexicalEnvironment(); + const merged = mergeConciseBodyLexicalEnvironment(visited, declarations, visited !== body); + if (forceBlockFunctionBody && !isBlock(merged)) { + return createBlock([ + createReturn(merged) + ]); + } + else { + return merged; + } + } + } + + function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody | FunctionBody { + const promiseConstructor = getEntityNameFromTypeNode(node.type); + const isArrowFunction = node.kind === SyntaxKind.ArrowFunction; + const hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0; + + // An async function is emit as an outer function that calls an inner + // generator function. To preserve lexical bindings, we pass the current + // `this` and `arguments` objects to `__awaiter`. The generator function + // passed to `__awaiter` is executed inside of the callback to the + // promise constructor. + // + // The emit for an async arrow without a lexical `arguments` binding might be: + // + // // input + // let a = async (b) => { await b; } + // + // // output + // let a = (b) => __awaiter(this, void 0, void 0, function* () { + // yield b; + // }); + // + // The emit for an async arrow with a lexical `arguments` binding might be: + // + // // input + // let a = async (b) => { await arguments[0]; } + // + // // output + // let a = (b) => __awaiter(this, arguments, void 0, function* (arguments) { + // yield arguments[0]; + // }); + // + // The emit for an async function expression without a lexical `arguments` binding + // might be: + // + // // input + // let a = async function (b) { + // await b; + // } + // + // // output + // let a = function (b) { + // return __awaiter(this, void 0, void 0, function* () { + // yield b; + // }); + // } + // + // The emit for an async function expression with a lexical `arguments` binding + // might be: + // + // // input + // let a = async function (b) { + // await arguments[0]; + // } + // + // // output + // let a = function (b) { + // return __awaiter(this, arguments, void 0, function* (_arguments) { + // yield _arguments[0]; + // }); + // } + // + // The emit for an async function expression with a lexical `arguments` binding + // and a return type annotation might be: + // + // // input + // let a = async function (b): MyPromise { + // await arguments[0]; + // } + // + // // output + // let a = function (b) { + // return __awaiter(this, arguments, MyPromise, function* (_arguments) { + // yield _arguments[0]; + // }); + // } + // + + if (!isArrowFunction) { + const statements: Statement[] = []; + + addNode(statements, + createReturn( + createAwaiterHelper( + hasLexicalArguments, + promiseConstructor, + transformFunctionBodyWorker(node.body) + ) + ) + ); + + const block = createBlock(statements); + + // Minor optimization, emit `_super` helper to capture `super` access in an arrow. + // This step isn't needed if we eventually transform this to ES5. + if (languageVersion >= ScriptTarget.ES6) { + if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuperBinding) { + enableExpressionSubstitutionForAsyncMethodsWithSuper(); + setNodeEmitFlags(block, NodeEmitFlags.EmitAdvancedSuperHelper); + } + else if (resolver.getNodeCheckFlags(node) & NodeCheckFlags.AsyncMethodWithSuper) { + enableExpressionSubstitutionForAsyncMethodsWithSuper(); + setNodeEmitFlags(block, NodeEmitFlags.EmitSuperHelper); + } + } + + return block; + } + else { + return createAwaiterHelper( + hasLexicalArguments, + promiseConstructor, + transformConciseBodyWorker(node.body, /*forceBlockFunctionBody*/ true) + ); + } + } + + function enableExpressionSubstitutionForAsyncMethodsWithSuper() { + if (!hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { + hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper = true; + + // We need to enable substitutions for call, property access, and element access + // if we need to rewrite super calls. + context.enableExpressionSubstitution(SyntaxKind.CallExpression); + context.enableExpressionSubstitution(SyntaxKind.PropertyAccessExpression); + context.enableExpressionSubstitution(SyntaxKind.ElementAccessExpression); + + // We need to be notified when entering and exiting declarations that bind super. + context.enableEmitNotification(SyntaxKind.ClassDeclaration); + context.enableEmitNotification(SyntaxKind.MethodDeclaration); + context.enableEmitNotification(SyntaxKind.GetAccessor); + context.enableEmitNotification(SyntaxKind.SetAccessor); + context.enableEmitNotification(SyntaxKind.Constructor); + } + } + + /** + * Visits a parameter declaration node. + * + * This function will be called when one of the following conditions are met: + * - The node has an accessibility modifier. + * + * @param node The parameter declaration node. + */ + function visitParameter(node: ParameterDeclaration) { + Debug.assert(!node.dotDotDotToken); + return createParameter( + visitNode(node.name, visitor, isBindingName), + visitNode(node.initializer, visitor, isExpression) + ); + } + + /** + * Visits a variable statement in a namespace. + * + * This function will be called when one of the following conditions are met: + * - The node is exported from a TypeScript namespace. + */ + function visitVariableStatement(node: VariableStatement) { + Debug.assert(isNamespaceExport(node)); + + const variables = getInitializedVariables(node.declarationList); + if (variables.length === 0) { + // elide statement if there are no initialized variables. + return undefined; + } + + return createStatement( + inlineExpressions( + map(variables, transformInitializedVariable) + ) + ); + } + + function getInitializedVariables(node: VariableDeclarationList) { + return filter(node.declarations, isInitializedVariable); + } + + function isInitializedVariable(node: VariableDeclaration) { + return node.initializer !== undefined; + } + + function transformInitializedVariable(node: VariableDeclaration): Expression { + const name = node.name; + if (isBindingPattern(name)) { + return flattenVariableDestructuringToExpression( + node, + hoistVariableDeclaration, + getNamespaceMemberName, + visitor + ); + } + else { + return createAssignment( + getNamespaceMemberName(name), + visitNode(node.initializer, visitor, isExpression) + ); + } + } + + /** + * Visits an enum declaration. + * + * This function will be called any time a TypeScript enum is encountered. + * + * @param node The enum declaration node. + */ + function visitEnumDeclaration(node: EnumDeclaration) { + if (shouldElideEnumDeclaration(node)) { + return undefined; + } + + const savedCurrentNamespaceLocalName = currentNamespaceLocalName; + const modifiers = visitNodes(node.modifiers, visitor, isModifier); + const statements: Statement[] = []; + + let location: TextRange = node; + if (!isNamespaceExport(node)) { + addNode(statements, + createVariableStatement( + modifiers, + createVariableDeclarationList([ + createVariableDeclaration(node.name) + ]), + location + ) + ); + location = undefined; + } + + const namespaceMemberName = getNamespaceMemberName(node.name); + currentNamespaceLocalName = getGeneratedNameForNode(node); + addNode(statements, + createStatement( + createCall( + createParen( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [createParameter(currentNamespaceLocalName)], + transformEnumBody(node) + ) + ), + [createLogicalOr( + namespaceMemberName, + createAssignment( + namespaceMemberName, + createObjectLiteral() + ) + )] + ), + location + ) + ); + + if (isNamespaceExport(node)) { + addNode(statements, + createVariableStatement( + /*modifiers*/ undefined, + createVariableDeclarationList([ + createVariableDeclaration(node.name, namespaceMemberName) + ]), + location + ) + ); + } + + currentNamespaceLocalName = savedCurrentNamespaceLocalName; + return createNodeArrayNode(statements); + } + + /** + * Transforms the body of an enum declaration. + * + * @param node The enum declaration node. + */ + function transformEnumBody(node: EnumDeclaration): Block { + const statements: Statement[] = []; + startLexicalEnvironment(); + addNodes(statements, map(node.members, transformEnumMember)); + addNodes(statements, endLexicalEnvironment()); + return createBlock(statements); + } + + /** + * Transforms an enum member into a statement. + * + * @param member The enum member node. + */ + function transformEnumMember(member: EnumMember) { + const name = getExpressionForPropertyName(member); + return createStatement( + createAssignment( + createElementAccess( + currentNamespaceLocalName, + createAssignment( + createElementAccess( + currentNamespaceLocalName, + name + ), + transformEnumMemberDeclarationValue(member) + ) + ), + name + ), + member + ); + } + + /** + * Transforms the value of an enum member. + * + * @param member The enum member node. + */ + function transformEnumMemberDeclarationValue(member: EnumMember): Expression { + let value = resolver.getConstantValue(member); + if (value !== undefined) { + return createLiteral(value); + } + else if (member.initializer) { + return visitNode(member.initializer, visitor, isExpression); + } + else { + return createVoidZero(); + } + } + + /** + * Determines whether to elide an enum declaration. + * + * @param node The enum declaration node. + */ + function shouldElideEnumDeclaration(node: EnumDeclaration) { + return isConst(node) + && !compilerOptions.preserveConstEnums + && !compilerOptions.isolatedModules; + } + + /** + * Visits an await expression. + * + * This function will be called any time a TypeScript await expression is encountered. + * + * @param node The await expression node. + */ + function visitAwaitExpression(node: AwaitExpression): Expression { + const expression = createYield( + visitNode(node.expression, visitor, isExpression), + node + ); + + return isRightmostExpression ? expression : createParen(expression); + } + + /** + * Visits a parenthesized expression that contains either a type assertion or an `as` + * expression. + * + * @param node The parenthesized expression node. + */ + function visitParenthesizedExpression(node: ParenthesizedExpression): Expression { + // Make sure we consider all nested cast expressions, e.g.: + // (-A).x; + const expression = visitNode(node.expression, visitor, isExpression); + if (currentParent.kind !== SyntaxKind.ArrowFunction) { + // We have an expression of the form: (SubExpr) + // Emitting this as (SubExpr) is really not desirable. We would like to emit the subexpr as is. + // Omitting the parentheses, however, could cause change in the semantics of the generated + // code if the casted expression has a lower precedence than the rest of the expression, e.g.: + // (new A).foo should be emitted as (new A).foo and not new A.foo + // (typeof A).toString() should be emitted as (typeof A).toString() and not typeof A.toString() + // new (A()) should be emitted as new (A()) and not new A() + // (function foo() { })() should be emitted as an IIF (function foo(){})() and not declaration function foo(){} () + if (expression.kind !== SyntaxKind.PrefixUnaryExpression && + expression.kind !== SyntaxKind.VoidExpression && + expression.kind !== SyntaxKind.TypeOfExpression && + expression.kind !== SyntaxKind.DeleteExpression && + expression.kind !== SyntaxKind.PostfixUnaryExpression && + expression.kind !== SyntaxKind.NewExpression && + !(expression.kind === SyntaxKind.CallExpression && currentParent.kind === SyntaxKind.NewExpression) && + !(expression.kind === SyntaxKind.FunctionExpression && currentParent.kind === SyntaxKind.CallExpression) && + !(expression.kind === SyntaxKind.NumericLiteral && currentParent.kind === SyntaxKind.PropertyAccessExpression)) { + return expression; + } + } + + return createParen(expression, node); + } + + /** + * Visits a module declaration node. + * + * This function will be called any time a TypeScript namespace (ModuleDeclaration) is encountered. + * + * @param node The module declaration node. + */ + function visitModuleDeclaration(node: ModuleDeclaration) { + if (shouldElideModuleDeclaration(node)) { + return undefined; + } + + Debug.assert(isIdentifier(node.name)); + + const savedCurrentNamespaceLocalName = currentNamespaceLocalName; + const modifiers = visitNodes(node.modifiers, visitor, isModifier); + const statements: Statement[] = []; + + let location = node; + if (!isModuleMergedWithClass(node)) { + // var x; + statements.push( + createVariableStatement( + modifiers, + createVariableDeclarationList([ + createVariableDeclaration(node.name) + ]), + location + ) + ); + location = undefined; + } + + let moduleParam: Expression = createLogicalOr( + getNamespaceMemberName(node.name), + createAssignment( + getNamespaceMemberName(node.name), + createObjectLiteral([]) + ) + ); + + if (isNamespaceExport(node)) { + moduleParam = createAssignment(cloneNode(node.name), moduleParam); + } + + currentNamespaceLocalName = getGeneratedNameForNode(node); + currentNamespace = node; + + // (function (x_1) { + // x_1.y = ...; + // })(x || (x = {})); + statements.push( + setOriginalNode( + createStatement( + createCall( + createParen( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + [createParameter(currentNamespaceLocalName)], + transformModuleBody(node) + ) + ), + [moduleParam] + ) + ), + node + ) + ); + + currentNamespaceLocalName = savedCurrentNamespaceLocalName; + return createNodeArrayNode(statements); + } + + /** + * Transforms the body of a module declaration. + * + * @param node The module declaration node. + */ + function transformModuleBody(node: ModuleDeclaration): Block { + const statements: Statement[] = []; + startLexicalEnvironment(); + const body = node.body; + if (body.kind === SyntaxKind.ModuleBlock) { + addNodes(statements, visitNodes((body).statements, namespaceElementVisitor, isStatement)); + } + else { + addNode(statements, visitModuleDeclaration(body)); + } + + addNodes(statements, endLexicalEnvironment()); + return createBlock(statements); + } + + /** + * Determines whether to elide a module declaration. + * + * @param node The module declaration node. + */ + function shouldElideModuleDeclaration(node: ModuleDeclaration) { + return !isInstantiatedModule(node, compilerOptions.preserveConstEnums || compilerOptions.isolatedModules); + } + + /** + * Determines whether a module declaration has a name that merges with a class declaration. + * + * @param node The module declaration node. + */ + function isModuleMergedWithClass(node: ModuleDeclaration) { + return !!(resolver.getNodeCheckFlags(getOriginalNode(node)) & NodeCheckFlags.LexicalModuleMergesWithClass); + } + + /** + * Visits an import equals declaration. + * + * @param node The import equals declaration node. + */ + function visitImportEqualsDeclaration(node: ImportEqualsDeclaration): OneOrMore { + Debug.assert(!isExternalModuleImportEqualsDeclaration(node)); + if (shouldElideImportEqualsDeclaration(node)) { + return undefined; + } + + const moduleReference = createExpressionFromEntityName(node.moduleReference); + if (isNamedExternalModuleExport(node) || !isNamespaceExport(node)) { + // export var ${name} = ${moduleReference}; + // var ${name} = ${moduleReference}; + return setOriginalNode( + createVariableStatement( + visitNodes(node.modifiers, visitor, isModifier), + createVariableDeclarationList([ + createVariableDeclaration(node.name, moduleReference) + ]), + node + ), + node + ); + } + else { + // exports.${name} = ${moduleReference}; + return setOriginalNode( + createNamespaceExport( + getSynthesizedNode(node.name), + moduleReference, + node + ), + node + ); + } + } + + /** + * Determines whether to elide an import equals declaration. + * + * @param node The import equals declaration node. + */ + function shouldElideImportEqualsDeclaration(node: ImportEqualsDeclaration) { + // preserve old compiler's behavior: emit 'var' for import declaration (even if we do not consider them referenced) when + // - current file is not external module + // - import declaration is top level and target is value imported by entity name + return !resolver.isReferencedAliasDeclaration(node) + && (isExternalModule(currentSourceFile) || !resolver.isTopLevelValueImportEqualsWithEntityName(node)); + } + + /** + * Gets a value indicating whether the node is exported from an external module. + * + * @param node The node to test. + */ + function isExternalModuleExport(node: Node) { + return currentNamespace === undefined + && (node.flags & NodeFlags.Export) !== 0; + } + + /** + * Gets a value indicating whether the node is a named export from an external module. + * + * @param node The node to test. + */ + function isNamedExternalModuleExport(node: Node) { + return isExternalModuleExport(node) + && (node.flags & NodeFlags.Default) === 0; + } + + /** + * Gets a value indicating whether the node is the default export of an external module. + * + * @param node The node to test. + */ + function isDefaultExternalModuleExport(node: Node) { + return isExternalModuleExport(node) + && (node.flags & NodeFlags.Default) !== 0; + } + + /** + * Gets a value indicating whether the node is exported from a namespace. + * + * @param node The node to test. + */ + function isNamespaceExport(node: Node) { + return currentNamespace !== undefined + && (node.flags & NodeFlags.Export) !== 0; + } + + /** + * Creates a statement for the provided expression. This is used in calls to `map`. + */ + function expressionToStatement(expression: Expression) { + return createStatement(expression, /*location*/ undefined); + } + + function createExportStatement(node: ClassExpression | ClassDeclaration | FunctionDeclaration): Statement { + const name = getDeclarationName(node); + if (currentNamespace) { + return createNamespaceExport(name, name); + } + else if (node.flags & NodeFlags.Default) { + return createExportDefault(name); + } + else { + return createModuleExport(name); + } + } + + function createNamespaceExport(exportName: Identifier, exportValue: Expression, location?: TextRange) { + return createStatement( + createAssignment( + getNamespaceMemberName(exportName), + exportValue + ), + location + ); + } + + function createModuleExport(exportName: Identifier) { + return createExportDeclaration( + createNamedExports([ + createExportSpecifier(exportName) + ]) + ); + } + + function getNamespaceMemberName(name: Identifier): Expression { + name = getSynthesizedNode(name); + return currentNamespaceLocalName + ? createPropertyAccess(currentNamespaceLocalName, name) + : name + } + + function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration) { + return node.name ? getSynthesizedNode(node.name) : getGeneratedNameForNode(node); + } + + function getClassPrototype(node: ClassExpression | ClassDeclaration) { + return createPropertyAccess(getDeclarationName(node), "prototype"); + } + + function getClassMemberPrefix(node: ClassExpression | ClassDeclaration, member: ClassElement) { + return member.flags & NodeFlags.Static + ? getDeclarationName(node) + : getClassPrototype(node); + } + + function substituteExpression(node: Expression): Expression { + node = previousExpressionSubstitution ? previousExpressionSubstitution(node) : node; + switch (node.kind) { + case SyntaxKind.Identifier: + return substituteExpressionIdentifier(node); + } + + if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper) { + switch (node.kind) { + case SyntaxKind.CallExpression: + return substituteCallExpression(node); + case SyntaxKind.PropertyAccessExpression: + return substitutePropertyAccessExpression(node); + case SyntaxKind.ElementAccessExpression: + return substituteElementAccessExpression(node); + } + } + return node; } + + function substituteExpressionIdentifier(node: Identifier) { + if (!nodeIsSynthesized(node) && resolver.getNodeCheckFlags(node) & NodeCheckFlags.BodyScopedClassBinding) { + // Due to the emit for class decorators, any reference to the class from inside of the class body + // must instead be rewritten to point to a temporary variable to avoid issues with the double-bind + // behavior of class names in ES6. + const original = getOriginalNode(node); + const declaration = resolver.getReferencedValueDeclaration(isIdentifier(original) ? original : node); + if (declaration) { + const classAlias = currentDecoratedClassAliases[getNodeId(declaration)]; + if (classAlias) { + return cloneNode(classAlias); + } + } + } + + return node; + } + + function substituteCallExpression(node: CallExpression) { + const expression = node.expression; + if (isSuperPropertyOrElementAccess(expression)) { + const flags = getSuperContainerAsyncMethodFlags(); + if (flags) { + const argumentExpression = isPropertyAccessExpression(expression) + ? substitutePropertyAccessExpression(expression) + : substituteElementAccessExpression(expression); + return createCall( + createPropertyAccess(argumentExpression, "call"), + [ + createThis(), + ...node.arguments + ] + ); + } + } + return node; + } + + function substitutePropertyAccessExpression(node: PropertyAccessExpression) { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + const flags = getSuperContainerAsyncMethodFlags(); + if (flags) { + return createSuperAccessInAsyncMethod( + createLiteral(node.name.text), + flags, + node + ); + } + } + + return node; + } + + function substituteElementAccessExpression(node: ElementAccessExpression) { + if (node.expression.kind === SyntaxKind.SuperKeyword) { + const flags = getSuperContainerAsyncMethodFlags(); + if (flags) { + return createSuperAccessInAsyncMethod( + node.argumentExpression, + flags, + node + ); + } + } + + return node; + } + + + function createSuperAccessInAsyncMethod(argumentExpression: Expression, flags: NodeCheckFlags, location: TextRange): LeftHandSideExpression { + if (flags & NodeCheckFlags.AsyncMethodWithSuperBinding) { + return createPropertyAccess( + createCall( + createIdentifier("_super"), + [argumentExpression] + ), + "value", + location + ); + } + else { + return createCall( + createIdentifier("_super"), + [argumentExpression], + location + ); + } + } + + function getSuperContainerAsyncMethodFlags() { + const container = lastOrUndefined(superContainerStack); + return container !== undefined + && resolver.getNodeCheckFlags(getOriginalNode(container)) & (NodeCheckFlags.AsyncMethodWithSuper | NodeCheckFlags.AsyncMethodWithSuperBinding); + } + + function onBeforeEmitNode(node: Node): void { + const kind = node.kind; + if (kind === SyntaxKind.ClassDeclaration && node.decorators) { + currentDecoratedClassAliases[getOriginalNodeId(node)] = decoratedClassAliases[getOriginalNodeId(node)]; + } + + if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + && (kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor)) { + + if (!superContainerStack) { + superContainerStack = []; + } + + superContainerStack.push(node); + } + } + + function onAfterEmitNode(node: Node): void { + const kind = node.kind; + if (kind === SyntaxKind.ClassDeclaration && node.decorators) { + currentDecoratedClassAliases[getOriginalNodeId(node)] = undefined; + } + + if (hasEnabledExpressionSubstitutionForAsyncMethodsWithSuper + && (kind === SyntaxKind.ClassDeclaration + || kind === SyntaxKind.Constructor + || kind === SyntaxKind.MethodDeclaration + || kind === SyntaxKind.GetAccessor + || kind === SyntaxKind.SetAccessor)) { + + if (superContainerStack) { + superContainerStack.pop(); + } + } + } + + function isRightmost(parentNode: Node, node: Node, wasRightmost: boolean) { + switch (parentNode.kind) { + case SyntaxKind.VariableDeclaration: + case SyntaxKind.Parameter: + case SyntaxKind.BindingElement: + case SyntaxKind.PropertyDeclaration: + case SyntaxKind.PropertyAssignment: + case SyntaxKind.ShorthandPropertyAssignment: + case SyntaxKind.ExpressionStatement: + case SyntaxKind.ArrayLiteralExpression: + case SyntaxKind.ThrowStatement: + case SyntaxKind.ReturnStatement: + case SyntaxKind.ForStatement: + case SyntaxKind.ForOfStatement: + case SyntaxKind.ForInStatement: + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + case SyntaxKind.SwitchStatement: + case SyntaxKind.CaseClause: + case SyntaxKind.WithStatement: + case SyntaxKind.Decorator: + case SyntaxKind.ExpressionWithTypeArguments: + case SyntaxKind.SpreadElementExpression: + case SyntaxKind.AsExpression: + case SyntaxKind.TypeAssertionExpression: + case SyntaxKind.EnumMember: + case SyntaxKind.JsxAttribute: + case SyntaxKind.JsxSpreadAttribute: + case SyntaxKind.JsxExpression: + return true; + case SyntaxKind.TemplateSpan: + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return node !== (parentNode).expression; + case SyntaxKind.BinaryExpression: + return wasRightmost && node === (parentNode).right; + case SyntaxKind.ConditionalExpression: + return wasRightmost && node === (parentNode).whenTrue; + default: + return wasRightmost; + } + } } } \ No newline at end of file diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 6cd3a666788..2ba3e4fa118 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1301,7 +1301,7 @@ namespace ts { export interface EnumMember extends Declaration { // This does include ComputedPropertyName, but the parser will give an error // if it parses a ComputedPropertyName in an EnumMember - name: DeclarationName; + name: PropertyName; initializer?: Expression; } @@ -2792,12 +2792,15 @@ namespace ts { /* @internal */ export const enum NodeEmitFlags { - EmitHelpers = 1 << 0, // Any emit helpers should be written to this node. - EmitExportStar = 1 << 1, // The export * helper should be written to this node. - UMDDefine = 1 << 2, // This node should be replaced with the UMD define helper. - NoLexicalEnvironment = 1 << 3, // A new LexicalEnvironment should *not* be introduced when emitting this node. - SingleLine = 1 << 4, // The contents of this node should be emit on a single line. - MultiLine = 1 << 5, // The contents of this node should be emit on multiple lines. + EmitEmitHelpers = 1 << 0, // Any emit helpers should be written to this node. + EmitExportStar = 1 << 1, // The export * helper should be written to this node. + EmitSuperHelper = 1 << 2, // Emit the basic _super helper for async methods. + EmitAdvancedSuperHelper = 1 << 3, // Emit the advanced _super helper for async methods. + UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. + NoLexicalEnvironment = 1 << 5, // A new LexicalEnvironment should *not* be introduced when emitting this node. + SingleLine = 1 << 6, // The contents of this node should be emit on a single line. + MultiLine = 1 << 7, // The contents of this node should be emit on multiple lines. + AdviseOnEmitNode = 1 << 8, // The node printer should invoke the onBeforeEmitNode and onAfterEmitNode callbacks when printing this node. } /** Additional context provided to `visitEachChild` */ @@ -2821,8 +2824,14 @@ namespace ts { getGeneratedNameForNode(node: Node): Identifier; nodeHasGeneratedName(node: Node): boolean; makeUniqueName(baseName: string): Identifier; + enableExpressionSubstitution(kind: SyntaxKind): void; + isExpressionSubstitutionEnabled(node: Node): boolean; identifierSubstitution?: (node: Identifier) => Identifier; expressionSubstitution?: (node: Expression) => Expression; + enableEmitNotification(kind: SyntaxKind): void; + isEmitNotificationEnabled(node: Node): boolean; + onBeforeEmitNode?: (node: Node) => void; + onAfterEmitNode?: (node: Node) => void; } /* @internal */ diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 15e288ef601..563921e7e9b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -1018,6 +1018,20 @@ namespace ts { && nodeCanBeDecorated(node); } + export function nodeOrChildIsDecorated(node: Node): boolean { + return nodeIsDecorated(node) || childIsDecorated(node); + } + + export function childIsDecorated(node: Node): boolean { + switch (node.kind) { + case SyntaxKind.ClassDeclaration: + return forEach((node).members, nodeOrChildIsDecorated); + case SyntaxKind.MethodDeclaration: + case SyntaxKind.SetAccessor: + return forEach((node).parameters, nodeIsDecorated); + } + } + export function isPartOfExpression(node: Node): boolean { switch (node.kind) { case SyntaxKind.SuperKeyword: @@ -2252,7 +2266,14 @@ namespace ts { return accessor && accessor.parameters.length > 0 && accessor.parameters[0].type; } - export function getAllAccessorDeclarations(declarations: NodeArray, accessor: AccessorDeclaration) { + export interface AllAccessorDeclarations { + firstAccessor: AccessorDeclaration; + secondAccessor: AccessorDeclaration; + getAccessor: AccessorDeclaration; + setAccessor: AccessorDeclaration; + } + + export function getAllAccessorDeclarations(declarations: NodeArray, accessor: AccessorDeclaration): AllAccessorDeclarations { let firstAccessor: AccessorDeclaration; let secondAccessor: AccessorDeclaration; let getAccessor: AccessorDeclaration; @@ -2816,7 +2837,6 @@ namespace ts { || kind === SyntaxKind.MethodDeclaration || kind === SyntaxKind.GetAccessor || kind === SyntaxKind.SetAccessor - || kind === SyntaxKind.MethodSignature || kind === SyntaxKind.IndexSignature; } @@ -2849,6 +2869,10 @@ namespace ts { return node.kind === SyntaxKind.BinaryExpression; } + export function isConditionalExpression(node: Node): node is ConditionalExpression { + return node.kind === SyntaxKind.ConditionalExpression; + } + export function isShortHandPropertyAssignment(node: Node): node is ShorthandPropertyAssignment { return node.kind === SyntaxKind.ShorthandPropertyAssignment; } @@ -3241,8 +3265,12 @@ namespace ts { return node.kind === SyntaxKind.NodeArrayNode; } - export function isModifiersArray(array: NodeArray): array is ModifiersArray { - return array.arrayKind === ArrayKind.ModifiersArray; + export function isNodeArray(array: T[]): array is NodeArray { + return (>array).arrayKind === ArrayKind.ModifiersArray; + } + + export function isModifiersArray(array: Modifier[]): array is ModifiersArray { + return (array).arrayKind === ArrayKind.ModifiersArray; } } diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 6e63524a23c..ee68fce3a6a 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -2,6 +2,8 @@ /* @internal */ namespace ts { + export type OneOrMore = T | NodeArrayNode; + /** * Describes an edge of a Node, used when traversing a syntax tree. */ @@ -472,7 +474,7 @@ namespace ts { * @param lift A callback to execute to lift a NodeArrayNode into a valid Node. * @param optional A value indicating whether the Node is optional. */ - export function visitNode(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, lift?: (node: NodeArray) => T, optional?: boolean): T { + export function visitNode(node: T, visitor: (node: Node) => Node, test?: (node: Node) => boolean, optional?: boolean, lift?: (node: NodeArray) => T): T { if (node === undefined) { return undefined; } @@ -490,6 +492,7 @@ namespace ts { Debug.assert(test === undefined || test(visited), "Wrong node type after visit."); aggregateTransformFlags(visited); + visited.original = node; return visited; } @@ -500,54 +503,55 @@ namespace ts { * @param visitor The callback used to visit a Node. * @param test A node test to execute for each node. */ - export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean): TArray { + export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): TArray; + export function visitNodes(nodes: T[], visitor: (node: T) => U): NodeArray; + export function visitNodes(nodes: T[], visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): NodeArray; + export function visitNodes>(nodes: TArray, visitor: (node: Node) => Node, test?: (node: Node) => boolean, start?: number, count?: number): TArray { if (nodes === undefined) { return undefined; } - let updated: NodeArray | ModifiersArray; - for (let i = 0, len = nodes.length; i < len; i++) { - const node = nodes[i]; + const len = nodes.length; + start = start !== undefined ? Math.max(start, 0) : 0; + count = count !== undefined ? Math.min(count, len - start) : len - start; + + let updated: T[]; + if (start > 0 || count < len) { + updated = []; + } + + for (let i = 0; i < count; i++) { + const node = nodes[i + start]; if (node === undefined) { continue; } - const visited = visitor(node); + const visited = >visitor(node); if (updated !== undefined || visited === undefined || visited !== node) { if (updated === undefined) { - updated = isModifiersArray(nodes) - ? createModifiersArray(nodes.slice(0, i), nodes.pos, nodes.end) - : createNodeArray(nodes.slice(0, i), nodes.pos, nodes.end); + updated = nodes.slice(0, i); } if (visited === undefined) { continue; } - if (isNodeArrayNode(visited)) { - spreadNodeArrayNode(visited, updated, test); + if (visited !== node) { + aggregateTransformFlags(visited); + visited.original = node; } - else if (visited !== undefined) { - Debug.assert(test === undefined || test(visited), "Wrong node type after visit."); - if (visited !== node) { - aggregateTransformFlags(visited); - } - updated.push(visited); - } + addNode(updated, visited, test); } } - if (updated && isModifiersArray(updated)) { - let flags: NodeFlags = 0; - for (const node of updated) { - flags |= modifierToFlag(node.kind); - } - - updated.flags = flags; + if (updated) { + return (isModifiersArray(nodes) + ? createModifiersArray(updated, /*location*/ nodes) + : createNodeArray(updated, /*location*/ nodes)); } - return updated || nodes; + return nodes; } /** @@ -606,6 +610,7 @@ namespace ts { if (updated !== node) { aggregateTransformFlags(updated); + updated.original = node; } return updated; @@ -621,24 +626,40 @@ namespace ts { function visitEdge(edge: NodeEdge, value: Node | NodeArray, visitor: (node: Node) => Node) { return isArray(value) ? visitNodes(>value, visitor, edge.test) - : visitNode(value, visitor, edge.test, edge.lift, edge.optional); + : visitNode(value, visitor, edge.test, edge.optional, edge.lift); } /** - * Spreads a NodeArrayNode into a NodeArray. + * Appends a node to an array. * - * @param source The source NodeArrayNode. - * @param dest The destination NodeArray. + * @param to The destination array. + * @param from The source Node or NodeArrayNode. * @param test The node test used to validate each node. */ - function spreadNodeArrayNode(source: NodeArrayNode, dest: NodeArray, test: (node: Node) => boolean) { - for (const element of source.nodes) { - if (element === undefined) { - continue; + export function addNode(to: T[], from: OneOrMore, test?: (node: Node) => boolean) { + if (to && from) { + if (isNodeArrayNode(from)) { + addNodes(to, from.nodes, test); } + else { + Debug.assert(test === undefined || test(from), "Wrong node type after visit."); + to.push(from); + } + } + } - Debug.assert(test === undefined || test(element), "Wrong node type after visit."); - dest.push(element); + /** + * Appends an array of nodes to an array. + * + * @param to The destination NodeArray. + * @param from The source array of Node or NodeArrayNode. + * @param test The node test used to validate each node. + */ + export function addNodes(to: T[], from: OneOrMore[], test?: (node: Node) => boolean) { + if (to && from) { + for (const node of from) { + addNode(to, node, test); + } } } @@ -695,14 +716,31 @@ namespace ts { */ function mergeFunctionLikeLexicalEnvironment(node: FunctionLikeDeclaration, declarations: Statement[]) { Debug.assert(node.body !== undefined); - if (node.body.kind === SyntaxKind.Block) { - node.body = mergeLexicalEnvironment(node.body, declarations, /*nodeIsMutable*/ false); + node.body = mergeConciseBodyLexicalEnvironment(node.body, declarations); + } + + export function mergeFunctionBodyLexicalEnvironment(body: FunctionBody, declarations: Statement[], nodeIsMutable?: boolean): FunctionBody { + if (declarations && declarations.length > 0) { + return mergeLexicalEnvironment(body, declarations, nodeIsMutable); + } + + return body; + } + + export function mergeConciseBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[], nodeIsMutable?: boolean): ConciseBody { + if (declarations && declarations.length > 0) { + if (isBlock(body)) { + return mergeLexicalEnvironment(body, declarations, nodeIsMutable); + } + else { + return createBlock([ + createReturn(body), + ...declarations + ]); + } } else { - node.body = createBlock([ - createReturn(node.body), - ...declarations - ]); + return body; } } @@ -717,7 +755,7 @@ namespace ts { * Merge generated declarations of a lexical environment into a NodeArray of Statement. */ function mergeStatements(statements: NodeArray, declarations: Statement[]) { - return createNodeArray(statements.concat(declarations), statements.pos, statements.end); + return createNodeArray(concatenate(statements, declarations), /*location*/ statements); } /**