From a2e0b19a94ecfbba3f05de5f7325acf946560876 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 25 Oct 2016 18:55:54 -0700 Subject: [PATCH] Emit for full down-level generators --- src/compiler/binder.ts | 6 +- src/compiler/comments.ts | 12 +- src/compiler/emitter.ts | 42 +- src/compiler/factory.ts | 284 ++++++--- src/compiler/transformer.ts | 183 +++--- src/compiler/transformers/destructuring.ts | 198 ++++-- src/compiler/transformers/es2015.ts | 665 ++++++++++++++------- src/compiler/transformers/es2017.ts | 97 ++- src/compiler/transformers/generators.ts | 24 +- src/compiler/transformers/jsx.ts | 18 +- src/compiler/transformers/module/es2015.ts | 58 ++ src/compiler/transformers/module/module.ts | 34 +- src/compiler/transformers/module/system.ts | 33 +- src/compiler/transformers/ts.ts | 362 +++++------ src/compiler/types.ts | 123 +++- src/compiler/utilities.ts | 30 +- src/compiler/visitor.ts | 137 +++-- 17 files changed, 1424 insertions(+), 882 deletions(-) diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 26463ca939a..adb0d264229 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -2698,7 +2698,7 @@ namespace ts { } // Currently, we only support generators that were originally async function bodies. - if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } @@ -2774,7 +2774,7 @@ namespace ts { // down-level generator. // Currently we do not support transforming any other generator fucntions // down level. - if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } } @@ -2811,7 +2811,7 @@ namespace ts { // down-level generator. // Currently we do not support transforming any other generator fucntions // down level. - if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { transformFlags |= TransformFlags.AssertGenerator; } diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index cd96981c093..dac87917700 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -25,6 +25,8 @@ namespace ts { let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; let hasWrittenComment = false; let disabled: boolean = compilerOptions.removeComments; + // let leadingCommentPositions: Map; + // let trailingCommentPositions: Map; return { reset, @@ -259,7 +261,8 @@ namespace ts { function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments - if (containerPos === -1 || pos !== containerPos) { + if ((containerPos === -1 || pos !== containerPos) /* && !leadingCommentPositions[pos] */) { + // leadingCommentPositions[pos] = true; if (hasDetachedComments(pos)) { forEachLeadingCommentWithoutDetachedComments(cb); } @@ -271,7 +274,8 @@ namespace ts { function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments - if (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) { + if ((containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) /*&& !trailingCommentPositions[end] */) { + // trailingCommentPositions[end] = true; forEachTrailingCommentRange(currentText, end, cb); } } @@ -281,6 +285,8 @@ namespace ts { currentText = undefined; currentLineMap = undefined; detachedCommentsInfo = undefined; + // leadingCommentPositions = undefined; + // trailingCommentPositions = undefined; } function setSourceFile(sourceFile: SourceFile) { @@ -288,6 +294,8 @@ namespace ts { currentText = currentSourceFile.text; currentLineMap = getLineStarts(currentSourceFile); detachedCommentsInfo = undefined; + // leadingCommentPositions = createMap(); + // trailingCommentPositions = createMap(); } function hasDetachedComments(pos: number) { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 5be5ae9a71d..dc8a0525c35 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1276,28 +1276,28 @@ namespace ts { writeToken(SyntaxKind.OpenParenToken, openParenPos, node); emitExpression(node.expression); writeToken(SyntaxKind.CloseParenToken, node.expression.end, node); - emitEmbeddedStatement(node.thenStatement); + emitEmbeddedStatement(node, node.thenStatement); if (node.elseStatement) { - writeLine(); + writeLineOrSpace(node); writeToken(SyntaxKind.ElseKeyword, node.thenStatement.end, node); if (node.elseStatement.kind === SyntaxKind.IfStatement) { write(" "); emit(node.elseStatement); } else { - emitEmbeddedStatement(node.elseStatement); + emitEmbeddedStatement(node, node.elseStatement); } } } function emitDoStatement(node: DoStatement) { write("do"); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); if (isBlock(node.statement)) { write(" "); } else { - writeLine(); + writeLineOrSpace(node); } write("while ("); @@ -1309,7 +1309,7 @@ namespace ts { write("while ("); emitExpression(node.expression); write(")"); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); } function emitForStatement(node: ForStatement) { @@ -1322,7 +1322,7 @@ namespace ts { write(";"); emitExpressionWithPrefix(" ", node.incrementor); write(")"); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); } function emitForInStatement(node: ForInStatement) { @@ -1333,7 +1333,7 @@ namespace ts { write(" in "); emitExpression(node.expression); writeToken(SyntaxKind.CloseParenToken, node.expression.end); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); } function emitForOfStatement(node: ForOfStatement) { @@ -1344,7 +1344,7 @@ namespace ts { write(" of "); emitExpression(node.expression); writeToken(SyntaxKind.CloseParenToken, node.expression.end); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); } function emitForBinding(node: VariableDeclarationList | Expression) { @@ -1380,7 +1380,7 @@ namespace ts { write("with ("); emitExpression(node.expression); write(")"); - emitEmbeddedStatement(node.statement); + emitEmbeddedStatement(node, node.statement); } function emitSwitchStatement(node: SwitchStatement) { @@ -1408,9 +1408,13 @@ namespace ts { function emitTryStatement(node: TryStatement) { write("try "); emit(node.tryBlock); - emit(node.catchClause); + if (node.catchClause) { + writeLineOrSpace(node); + emit(node.catchClause); + } + if (node.finallyBlock) { - writeLine(); + writeLineOrSpace(node); write("finally "); emit(node.finallyBlock); } @@ -1880,7 +1884,6 @@ namespace ts { } function emitCatchClause(node: CatchClause) { - writeLine(); const openParenPos = writeToken(SyntaxKind.CatchKeyword, node.pos); write(" "); writeToken(SyntaxKind.OpenParenToken, openParenPos); @@ -2087,8 +2090,8 @@ namespace ts { } } - function emitEmbeddedStatement(node: Statement) { - if (isBlock(node)) { + function emitEmbeddedStatement(parent: Node, node: Statement) { + if (isBlock(node) || getEmitFlags(parent) & EmitFlags.SingleLine) { write(" "); emit(node); } @@ -2253,6 +2256,15 @@ namespace ts { } } + function writeLineOrSpace(node: Node) { + if (getEmitFlags(node) & EmitFlags.SingleLine) { + write(" "); + } + else { + writeLine(); + } + } + function writeIfAny(nodes: NodeArray, text: string) { if (nodes && nodes.length > 0) { write(text); diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 7791923be3f..a6a66cddafd 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -6,7 +6,7 @@ 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, flags?: NodeFlags): Node { + function createNode(kind: SyntaxKind, location?: TextRange): Node { const ConstructorForKind = kind === SyntaxKind.SourceFile ? (SourceFileConstructor || (SourceFileConstructor = objectAllocator.getSourceFileConstructor())) : (NodeConstructor || (NodeConstructor = objectAllocator.getNodeConstructor())); @@ -15,7 +15,7 @@ namespace ts { ? new ConstructorForKind(kind, location.pos, location.end) : new ConstructorForKind(kind, /*pos*/ -1, /*end*/ -1); - node.flags = flags | NodeFlags.Synthesized; + node.flags |= NodeFlags.Synthesized; return node; } @@ -65,7 +65,7 @@ namespace ts { } export function createSynthesizedNodeArray(elements?: T[]): NodeArray { - return createNodeArray(elements, /*location*/ undefined); + return createNodeArray(isNodeArray(elements) ? elements.slice(0) : elements, /*location*/ undefined); } /** @@ -75,7 +75,8 @@ namespace ts { // We don't use "clone" from core.ts here, as we need to preserve the prototype chain of // the original node. We also need to exclude specific properties and only include own- // properties (to skip members already defined on the shared prototype). - const clone = createNode(node.kind, /*location*/ undefined, node.flags); + const clone = createNode(node.kind, /*location*/ undefined); + clone.flags |= node.flags; setOriginalNode(clone, node); for (const key in node) { @@ -109,20 +110,20 @@ namespace ts { export function createLiteral(value: string | number | boolean, location?: TextRange): PrimaryExpression; export function createLiteral(value: string | number | boolean | StringLiteral | Identifier, location?: TextRange): PrimaryExpression { if (typeof value === "number") { - const node = createNode(SyntaxKind.NumericLiteral, location, /*flags*/ undefined); + const node = createNode(SyntaxKind.NumericLiteral, location); node.text = value.toString(); return node; } else if (typeof value === "boolean") { - return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location, /*flags*/ undefined); + return createNode(value ? SyntaxKind.TrueKeyword : SyntaxKind.FalseKeyword, location); } else if (typeof value === "string") { - const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined); + const node = createNode(SyntaxKind.StringLiteral, location); node.text = value; return node; } else if (value) { - const node = createNode(SyntaxKind.StringLiteral, location, /*flags*/ undefined); + const node = createNode(SyntaxKind.StringLiteral, location); node.textSourceNode = value; node.text = value.text; return node; @@ -226,8 +227,8 @@ namespace ts { // Signature elements - export function createParameter(decorators: Decorator[], modifiers: Modifier[], dotDotDotToken: DotDotDotToken, name: string | Identifier | BindingPattern, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.Parameter, location, flags); + export function createParameter(decorators: Decorator[], modifiers: Modifier[], dotDotDotToken: DotDotDotToken, name: string | Identifier | BindingPattern, questionToken?: QuestionToken, type?: TypeNode, initializer?: Expression, location?: TextRange) { + const node = createNode(SyntaxKind.Parameter, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.dotDotDotToken = dotDotDotToken; @@ -240,7 +241,7 @@ namespace ts { export function updateParameter(node: ParameterDeclaration, decorators: Decorator[], modifiers: Modifier[], name: BindingName, type: TypeNode, initializer: Expression) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name || node.type !== type || node.initializer !== initializer) { - return updateNode(createParameter(decorators, modifiers, node.dotDotDotToken, name, node.questionToken, type, initializer, /*location*/ node, /*flags*/ node.flags), node); + return updateNode(createParameter(decorators, modifiers, node.dotDotDotToken, name, node.questionToken, type, initializer, /*location*/ node), node); } return node; @@ -266,8 +267,8 @@ namespace ts { return node; } - export function createMethod(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | PropertyName, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.MethodDeclaration, location, flags); + export function createMethod(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | PropertyName, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.MethodDeclaration, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.asteriskToken = asteriskToken; @@ -281,13 +282,13 @@ namespace ts { export function updateMethod(node: MethodDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name || node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type || node.body !== body) { - return updateNode(createMethod(decorators, modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node, node.flags), node); + return updateNode(createMethod(decorators, modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node), node); } return node; } - export function createConstructor(decorators: Decorator[], modifiers: Modifier[], parameters: ParameterDeclaration[], body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.Constructor, location, flags); + export function createConstructor(decorators: Decorator[], modifiers: Modifier[], parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.Constructor, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.typeParameters = undefined; @@ -299,13 +300,13 @@ namespace ts { export function updateConstructor(node: ConstructorDeclaration, decorators: Decorator[], modifiers: Modifier[], parameters: ParameterDeclaration[], body: Block) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.parameters !== parameters || node.body !== body) { - return updateNode(createConstructor(decorators, modifiers, parameters, body, /*location*/ node, node.flags), node); + return updateNode(createConstructor(decorators, modifiers, parameters, body, /*location*/ node), node); } return node; } - export function createGetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.GetAccessor, location, flags); + export function createGetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.GetAccessor, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -318,13 +319,13 @@ namespace ts { export function updateGetAccessor(node: GetAccessorDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, parameters: ParameterDeclaration[], type: TypeNode, body: Block) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name || node.parameters !== parameters || node.type !== type || node.body !== body) { - return updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body, /*location*/ node, node.flags), node); + return updateNode(createGetAccessor(decorators, modifiers, name, parameters, type, body, /*location*/ node), node); } return node; } - export function createSetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.SetAccessor, location, flags); + export function createSetAccessor(decorators: Decorator[], modifiers: Modifier[], name: string | PropertyName, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.SetAccessor, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -336,7 +337,7 @@ namespace ts { export function updateSetAccessor(node: SetAccessorDeclaration, decorators: Decorator[], modifiers: Modifier[], name: PropertyName, parameters: ParameterDeclaration[], body: Block) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name || node.parameters !== parameters || node.body !== body) { - return updateNode(createSetAccessor(decorators, modifiers, name, parameters, body, /*location*/ node, node.flags), node); + return updateNode(createSetAccessor(decorators, modifiers, name, parameters, body, /*location*/ node), node); } return node; } @@ -420,8 +421,8 @@ namespace ts { return node; } - export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.PropertyAccessExpression, location, flags); + export function createPropertyAccess(expression: Expression, name: string | Identifier, location?: TextRange) { + const node = createNode(SyntaxKind.PropertyAccessExpression, location); node.expression = parenthesizeForAccess(expression); (node.emitNode || (node.emitNode = {})).flags |= EmitFlags.NoIndentation; node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -430,7 +431,7 @@ namespace ts { export function updatePropertyAccess(node: PropertyAccessExpression, expression: Expression, name: Identifier) { if (node.expression !== expression || node.name !== name) { - const propertyAccess = createPropertyAccess(expression, name, /*location*/ node, node.flags); + const propertyAccess = createPropertyAccess(expression, name, /*location*/ node); // Because we are updating existed propertyAccess we want to inherit its emitFlags instead of using default from createPropertyAccess (propertyAccess.emitNode || (propertyAccess.emitNode = {})).flags = getEmitFlags(node); return updateNode(propertyAccess, node); @@ -452,8 +453,8 @@ namespace ts { return node; } - export function createCall(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[], location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.CallExpression, location, flags); + export function createCall(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.CallExpression, location); node.expression = parenthesizeForAccess(expression); if (typeArguments) { node.typeArguments = createNodeArray(typeArguments); @@ -465,13 +466,13 @@ namespace ts { export function updateCall(node: CallExpression, expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { if (expression !== node.expression || typeArguments !== node.typeArguments || argumentsArray !== node.arguments) { - return updateNode(createCall(expression, typeArguments, argumentsArray, /*location*/ node, node.flags), node); + return updateNode(createCall(expression, typeArguments, argumentsArray, /*location*/ node), node); } return node; } - export function createNew(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[], location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.NewExpression, location, flags); + export function createNew(expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[], location?: TextRange) { + const node = createNode(SyntaxKind.NewExpression, location); node.expression = parenthesizeForNew(expression); node.typeArguments = typeArguments ? createNodeArray(typeArguments) : undefined; node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; @@ -480,7 +481,7 @@ namespace ts { export function updateNew(node: NewExpression, expression: Expression, typeArguments: TypeNode[], argumentsArray: Expression[]) { if (node.expression !== expression || node.typeArguments !== typeArguments || node.arguments !== argumentsArray) { - return updateNode(createNew(expression, typeArguments, argumentsArray, /*location*/ node, node.flags), node); + return updateNode(createNew(expression, typeArguments, argumentsArray, /*location*/ node), node); } return node; } @@ -512,8 +513,8 @@ namespace ts { return node; } - export function createFunctionExpression(modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.FunctionExpression, location, flags); + export function createFunctionExpression(modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionExpression, location); node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.asteriskToken = asteriskToken; node.name = typeof name === "string" ? createIdentifier(name) : name; @@ -526,13 +527,13 @@ namespace ts { export function updateFunctionExpression(node: FunctionExpression, modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { if (node.name !== name || node.modifiers !== modifiers || node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type || node.body !== body) { - return updateNode(createFunctionExpression(modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node, node.flags), node); + return updateNode(createFunctionExpression(modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node), node); } return node; } - export function createArrowFunction(modifiers: Modifier[], typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, equalsGreaterThanToken: EqualsGreaterThanToken, body: ConciseBody, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.ArrowFunction, location, flags); + export function createArrowFunction(modifiers: Modifier[], typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, equalsGreaterThanToken: EqualsGreaterThanToken, body: ConciseBody, location?: TextRange) { + const node = createNode(SyntaxKind.ArrowFunction, location); node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.typeParameters = typeParameters ? createNodeArray(typeParameters) : undefined; node.parameters = createNodeArray(parameters); @@ -544,7 +545,7 @@ namespace ts { export function updateArrowFunction(node: ArrowFunction, modifiers: Modifier[], typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: ConciseBody) { if (node.modifiers !== modifiers || node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type || node.body !== body) { - return updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, node.equalsGreaterThanToken, body, /*location*/ node, node.flags), node); + return updateNode(createArrowFunction(modifiers, typeParameters, parameters, type, node.equalsGreaterThanToken, body, /*location*/ node), node); } return node; } @@ -648,7 +649,7 @@ namespace ts { export function createConditional(condition: Expression, questionToken: QuestionToken, whenTrue: Expression, colonToken: ColonToken, whenFalse: Expression, location?: TextRange) { const node = createNode(SyntaxKind.ConditionalExpression, location); - node.condition = condition; + node.condition = parenthesizeConditionalHead(condition); node.questionToken = questionToken; node.whenTrue = whenTrue; node.colonToken = colonToken; @@ -760,8 +761,8 @@ namespace ts { // Element - export function createBlock(statements: Statement[], location?: TextRange, multiLine?: boolean, flags?: NodeFlags): Block { - const block = createNode(SyntaxKind.Block, location, flags); + export function createBlock(statements: Statement[], location?: TextRange, multiLine?: boolean): Block { + const block = createNode(SyntaxKind.Block, location); block.statements = createNodeArray(statements); if (multiLine) { block.multiLine = true; @@ -771,14 +772,14 @@ namespace ts { export function updateBlock(node: Block, statements: Statement[]) { if (statements !== node.statements) { - return updateNode(createBlock(statements, /*location*/ node, node.multiLine, node.flags), node); + return updateNode(createBlock(statements, /*location*/ node, node.multiLine), node); } return node; } - export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList | VariableDeclaration[], location?: TextRange, flags?: NodeFlags): VariableStatement { - const node = createNode(SyntaxKind.VariableStatement, location, flags); + export function createVariableStatement(modifiers: Modifier[], declarationList: VariableDeclarationList | VariableDeclaration[], location?: TextRange): VariableStatement { + const node = createNode(SyntaxKind.VariableStatement, location); node.decorators = undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.declarationList = isArray(declarationList) ? createVariableDeclarationList(declarationList) : declarationList; @@ -787,13 +788,14 @@ namespace ts { export function updateVariableStatement(node: VariableStatement, modifiers: Modifier[], declarationList: VariableDeclarationList): VariableStatement { if (node.modifiers !== modifiers || node.declarationList !== declarationList) { - return updateNode(createVariableStatement(modifiers, declarationList, /*location*/ node, node.flags), node); + return updateNode(createVariableStatement(modifiers, declarationList, /*location*/ node), node); } return node; } export function createVariableDeclarationList(declarations: VariableDeclaration[], location?: TextRange, flags?: NodeFlags): VariableDeclarationList { - const node = createNode(SyntaxKind.VariableDeclarationList, location, flags); + const node = createNode(SyntaxKind.VariableDeclarationList, location); + node.flags |= flags; node.declarations = createNodeArray(declarations); return node; } @@ -805,8 +807,8 @@ namespace ts { return node; } - export function createVariableDeclaration(name: string | BindingPattern | Identifier, type?: TypeNode, initializer?: Expression, location?: TextRange, flags?: NodeFlags): VariableDeclaration { - const node = createNode(SyntaxKind.VariableDeclaration, location, flags); + export function createVariableDeclaration(name: string | BindingPattern | Identifier, type?: TypeNode, initializer?: Expression, location?: TextRange): VariableDeclaration { + const node = createNode(SyntaxKind.VariableDeclaration, location); node.name = typeof name === "string" ? createIdentifier(name) : name; node.type = type; node.initializer = initializer !== undefined ? parenthesizeExpressionForList(initializer) : undefined; @@ -815,7 +817,7 @@ namespace ts { export function updateVariableDeclaration(node: VariableDeclaration, name: BindingName, type: TypeNode, initializer: Expression) { if (node.name !== name || node.type !== type || node.initializer !== initializer) { - return updateNode(createVariableDeclaration(name, type, initializer, /*location*/ node, node.flags), node); + return updateNode(createVariableDeclaration(name, type, initializer, /*location*/ node), node); } return node; } @@ -824,15 +826,15 @@ namespace ts { return createNode(SyntaxKind.EmptyStatement, location); } - export function createStatement(expression: Expression, location?: TextRange, flags?: NodeFlags): ExpressionStatement { - const node = createNode(SyntaxKind.ExpressionStatement, location, flags); + export function createStatement(expression: Expression, location?: TextRange): ExpressionStatement { + const node = createNode(SyntaxKind.ExpressionStatement, location); node.expression = parenthesizeExpressionForExpressionStatement(expression); return node; } export function updateStatement(node: ExpressionStatement, expression: Expression) { if (node.expression !== expression) { - return updateNode(createStatement(expression, /*location*/ node, node.flags), node); + return updateNode(createStatement(expression, /*location*/ node), node); } return node; @@ -882,7 +884,7 @@ namespace ts { } export function createFor(initializer: ForInitializer, condition: Expression, incrementor: Expression, statement: Statement, location?: TextRange) { - const node = createNode(SyntaxKind.ForStatement, location, /*flags*/ undefined); + const node = createNode(SyntaxKind.ForStatement, location); node.initializer = initializer; node.condition = condition; node.incrementor = incrementor; @@ -1053,8 +1055,8 @@ namespace ts { return node; } - export function createFunctionDeclaration(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange, flags?: NodeFlags) { - const node = createNode(SyntaxKind.FunctionDeclaration, location, flags); + export function createFunctionDeclaration(decorators: Decorator[], modifiers: Modifier[], asteriskToken: AsteriskToken, name: string | Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block, location?: TextRange) { + const node = createNode(SyntaxKind.FunctionDeclaration, location); node.decorators = decorators ? createNodeArray(decorators) : undefined; node.modifiers = modifiers ? createNodeArray(modifiers) : undefined; node.asteriskToken = asteriskToken; @@ -1068,7 +1070,7 @@ namespace ts { export function updateFunctionDeclaration(node: FunctionDeclaration, decorators: Decorator[], modifiers: Modifier[], name: Identifier, typeParameters: TypeParameterDeclaration[], parameters: ParameterDeclaration[], type: TypeNode, body: Block) { if (node.decorators !== decorators || node.modifiers !== modifiers || node.name !== name || node.typeParameters !== typeParameters || node.parameters !== parameters || node.type !== type || node.body !== body) { - return updateNode(createFunctionDeclaration(decorators, modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node, node.flags), node); + return updateNode(createFunctionDeclaration(decorators, modifiers, node.asteriskToken, name, typeParameters, parameters, type, body, /*location*/ node), node); } return node; } @@ -1361,9 +1363,11 @@ namespace ts { return node; } - export function createCatchClause(variableDeclaration: string | VariableDeclaration, block: Block, location?: TextRange) { + export function createCatchClause(variableDeclaration: string | Identifier | VariableDeclaration, block: Block, location?: TextRange) { const node = createNode(SyntaxKind.CatchClause, location); - node.variableDeclaration = typeof variableDeclaration === "string" ? createVariableDeclaration(variableDeclaration) : variableDeclaration; + node.variableDeclaration = typeof variableDeclaration === "string" || isIdentifier(variableDeclaration) + ? createVariableDeclaration(variableDeclaration) + : variableDeclaration; node.block = block; return node; } @@ -1410,7 +1414,8 @@ namespace ts { export function updateSourceFileNode(node: SourceFile, statements: Statement[]) { if (node.statements !== statements) { - const updated = createNode(SyntaxKind.SourceFile, /*location*/ node, node.flags); + const updated = createNode(SyntaxKind.SourceFile, /*location*/ node); + updated.flags |= node.flags; updated.statements = createNodeArray(statements); updated.endOfFileToken = node.endOfFileToken; updated.fileName = node.fileName; @@ -1680,27 +1685,125 @@ namespace ts { // Helpers - export interface EmitHelperState { - currentSourceFile: SourceFile; - compilerOptions: CompilerOptions; - requestedHelpers?: EmitHelper[]; + export function getHelperName(name: string) { + return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); } - export function getHelperName(helperState: EmitHelperState, name: string) { - const externalHelpersModuleName = getOrCreateExternalHelpersModuleName(helperState.currentSourceFile, helperState.compilerOptions); - return externalHelpersModuleName - ? createPropertyAccess(externalHelpersModuleName, name) - : createIdentifier(name); + const valuesHelper: EmitHelper = { + name: "typescript:values", + scoped: false, + text: ` + var __values = (this && this.__values) || function (o) { + var i = o.__iterator__ || 0, d; + return i ? i.call(o) : { next: function () { return { done: d = d || i >= o.length, value: d ? void 0 : o[i++] }; } }; + };` + }; + + export function createValuesHelper(context: TransformationContext, expression: Expression, location?: TextRange) { + context.requestEmitHelper(valuesHelper); + return createCall( + getHelperName("__values"), + /*typeArguments*/ undefined, + [expression], + location + ); } - export function requestEmitHelper(helperState: EmitHelperState, helper: EmitHelper) { - if (!contains(helperState.requestedHelpers, helper)) { - helperState.requestedHelpers = append(helperState.requestedHelpers, helper); - } + const stepHelper: EmitHelper = { + name: "typescript:step", + scoped: false, + text: ` + var __step = (this && this.__step) || function (r) { + return !(r.done || (r.done = (r.result = r.iterator.next()).done)); + };` + }; + + export function createStepHelper(context: TransformationContext, iteratorRecord: Expression, location?: TextRange) { + context.requestEmitHelper(stepHelper); + return createCall( + getHelperName("__step"), + /*typeArguments*/ undefined, + [iteratorRecord], + location + ); + } + + const closeHelper: EmitHelper = { + name: "typescript:close", + scoped: false, + text: ` + var __close = (this && this.__close) || function (r) { + var m = !(r && r.done) && r.iterator["return"]; + if (m) m.call(r.iterator); + };` + }; + + export function createCloseHelper(context: TransformationContext, iteratorRecord: Expression, location?: TextRange) { + context.requestEmitHelper(closeHelper); + return createCall( + getHelperName("__close"), + /*typeArguments*/ undefined, + [iteratorRecord], + location + ); + } + + const readHelper: EmitHelper = { + name: "typescript:read", + scoped: false, + text: ` + var __read = (this && this.__read) || function (o, n) { + var m = o.__iterator__; + if (!m) return o; + var r = { iterator: m.call(o) }, ar = [], e; + try { while ((n === void 0 || n-- > 0) && __step(r)) ar.push(r.result.value); } + catch (error) { e = { error: error }; } + finally { try { __close(r); } finally { if (e) throw e.error; } } + return ar; + };` + }; + + export function createReadHelper(context: TransformationContext, iteratorRecord: Expression, count: number | undefined, location?: TextRange) { + context.requestEmitHelper(stepHelper); + context.requestEmitHelper(readHelper); + context.requestEmitHelper(closeHelper); + return createCall( + getHelperName("__read"), + /*typeArguments*/ undefined, + count !== undefined + ? [iteratorRecord, createLiteral(count)] + : [iteratorRecord], + location + ); + } + + const spreadHelper: EmitHelper = { + name: "typescript:spread", + scoped: false, + text: ` + var __spread = (this && this.__spread) || function () { + for (var ar = [], i = 0; i < arguments.length; i++) ar = ar.concat(__read(arguments[i])); + return ar; + };` + }; + + export function createSpreadHelper(context: TransformationContext, argumentList: Expression[], location?: TextRange) { + context.requestEmitHelper(readHelper); + context.requestEmitHelper(spreadHelper); + return createCall( + getHelperName("__spread"), + /*typeArguments*/ undefined, + argumentList, + location + ); } // Utilities + export function toFunctionBody(node: ConciseBody) { + return isBlock(node) ? node : createBlock([createReturn(node, /*location*/ node)], /*location*/ node); + } + export interface CallBinding { target: LeftHandSideExpression; thisArg: Expression; @@ -2063,7 +2166,7 @@ namespace ts { * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives * @param visitor: Optional callback used to visit any custom prologue directives. */ - export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean, visitor?: (node: Node) => VisitResult): number { + export function addPrologueDirectives(target: Statement[], source: Statement[], ensureUseStrict?: boolean, ignoreCustomPrologue?: boolean, visitor?: (node: Node) => VisitResult): number { Debug.assert(target.length === 0, "Prologue directives should be at the first statement in the target statements array"); let foundUseStrict = false; let statementOffset = 0; @@ -2084,19 +2187,28 @@ namespace ts { if (ensureUseStrict && !foundUseStrict) { target.push(startOnNewLine(createStatement(createLiteral("use strict")))); } - while (statementOffset < numStatements) { - const statement = source[statementOffset]; - if (getEmitFlags(statement) & EmitFlags.CustomPrologue) { - target.push(visitor ? visitNode(statement, visitor, isStatement) : statement); + if (!ignoreCustomPrologue) { + while (statementOffset < numStatements) { + const statement = source[statementOffset]; + if (getEmitFlags(statement) & EmitFlags.CustomPrologue) { + target.push(visitor ? visitNode(statement, visitor, isStatement) : statement); + } + else { + break; + } + statementOffset++; } - else { - break; - } - statementOffset++; } return statementOffset; } + export function startsWithUseStrict(statements: Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); + } + /** * Ensures "use strict" directive is added * @@ -2126,6 +2238,16 @@ namespace ts { return statements; } + export function parenthesizeConditionalHead(condition: Expression) { + const conditionalPrecedence = getOperatorPrecedence(SyntaxKind.ConditionalExpression, SyntaxKind.QuestionToken); + const emittedCondition = skipPartiallyEmittedExpressions(condition); + const conditionPrecedence = getExpressionPrecedence(emittedCondition); + if (compareValues(conditionPrecedence, conditionalPrecedence) === Comparison.LessThan) { + return createParen(condition); + } + return condition; + } + /** * Wraps the operand to a BinaryExpression in parentheses if they are needed to preserve the intended * order of operations. diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index ee9c081e6eb..f0d39d3bd68 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -26,84 +26,6 @@ namespace ts { EmitNotifications = 1 << 1, } - export interface TransformationResult { - /** - * Gets the transformed source files. - */ - transformed: SourceFile[]; - - /** - * Emits the substitute for a node, if one is available; otherwise, emits the node. - * - * @param emitContext The current emit context. - * @param node The node to substitute. - * @param emitCallback A callback used to emit the node or its substitute. - */ - emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; - - /** - * Emits a node with possible notification. - * - * @param emitContext The current emit context. - * @param node The node to emit. - * @param emitCallback A callback used to emit the node. - */ - emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; - } - - export interface TransformationContext extends LexicalEnvironment { - getCompilerOptions(): CompilerOptions; - getEmitResolver(): EmitResolver; - getEmitHost(): EmitHost; - - /** - * Hoists a function declaration to the containing scope. - */ - hoistFunctionDeclaration(node: FunctionDeclaration): void; - - /** - * Hoists a variable declaration to the containing scope. - */ - hoistVariableDeclaration(node: Identifier): void; - - /** - * Enables expression substitutions in the pretty printer for the provided SyntaxKind. - */ - enableSubstitution(kind: SyntaxKind): void; - - /** - * Determines whether expression substitutions are enabled for the provided node. - */ - isSubstitutionEnabled(node: Node): boolean; - - /** - * Hook used by transformers to substitute expressions just before they - * are emitted by the pretty printer. - */ - onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node; - - /** - * Enables before/after emit notifications in the pretty printer for the provided - * SyntaxKind. - */ - enableEmitNotification(kind: SyntaxKind): void; - - /** - * Determines whether before/after emit notifications should be raised in the pretty - * printer when it emits a node. - */ - isEmitNotificationEnabled(node: Node): boolean; - - /** - * Hook used to allow transformers to capture state before or after - * the printer emits a node. - */ - onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void; - } - - /* @internal */ - export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; - export function getTransformers(compilerOptions: CompilerOptions) { const jsx = compilerOptions.jsx; const languageVersion = getEmitScriptTarget(compilerOptions); @@ -149,14 +71,19 @@ namespace ts { * @param transforms An array of Transformers. */ export function transformFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformers: Transformer[]): TransformationResult { - const lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; - const lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; const enabledSyntaxKindFeatures = new Array(SyntaxKind.Count); + let scopeModificationDisabled: boolean; + + let lexicalEnvironmentVariableDeclarations: VariableDeclaration[]; + let lexicalEnvironmentFunctionDeclarations: FunctionDeclaration[]; + let lexicalEnvironmentVariableDeclarationsStack: VariableDeclaration[][] = []; + let lexicalEnvironmentFunctionDeclarationsStack: FunctionDeclaration[][] = []; let lexicalEnvironmentStackOffset = 0; - let hoistedVariableDeclarations: VariableDeclaration[]; - let hoistedFunctionDeclarations: FunctionDeclaration[]; - let lexicalEnvironmentDisabled: boolean; + let lexicalEnvironmentSuspended = false; + + let nonScopedEmitHelpers: EmitHelper[]; + let scopedEmitHelpers: EmitHelper[]; // The transformation context is provided to each transformer as part of transformer // initialization. @@ -167,7 +94,11 @@ namespace ts { hoistVariableDeclaration, hoistFunctionDeclaration, startLexicalEnvironment, + suspendLexicalEnvironment, + resumeLexicalEnvironment, endLexicalEnvironment, + requestEmitHelper, + readEmitHelpers, onSubstituteNode: (_emitContext, node) => node, enableSubstitution, isSubstitutionEnabled, @@ -183,7 +114,7 @@ namespace ts { const transformed = map(sourceFiles, transformSourceFile); // Disable modification of the lexical environment. - lexicalEnvironmentDisabled = true; + scopeModificationDisabled = true; return { transformed, @@ -278,13 +209,13 @@ namespace ts { * Records a hoisted variable declaration for the provided name within a lexical environment. */ function hoistVariableDeclaration(name: Identifier): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); const decl = createVariableDeclaration(name); - if (!hoistedVariableDeclarations) { - hoistedVariableDeclarations = [decl]; + if (!lexicalEnvironmentVariableDeclarations) { + lexicalEnvironmentVariableDeclarations = [decl]; } else { - hoistedVariableDeclarations.push(decl); + lexicalEnvironmentVariableDeclarations.push(decl); } } @@ -292,12 +223,12 @@ namespace ts { * Records a hoisted function declaration within a lexical environment. */ function hoistFunctionDeclaration(func: FunctionDeclaration): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot modify the lexical environment during the print phase."); - if (!hoistedFunctionDeclarations) { - hoistedFunctionDeclarations = [func]; + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); + if (!lexicalEnvironmentFunctionDeclarations) { + lexicalEnvironmentFunctionDeclarations = [func]; } else { - hoistedFunctionDeclarations.push(func); + lexicalEnvironmentFunctionDeclarations.push(func); } } @@ -306,17 +237,29 @@ namespace ts { * are pushed onto a stack, and the related storage variables are reset. */ function startLexicalEnvironment(): void { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot start a lexical environment during the print phase."); + Debug.assert(!scopeModificationDisabled, "Cannot start a lexical environment during the print phase."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); + // Save the current lexical environment. Rather than resizing the array we adjust the // stack size variable. This allows us to reuse existing array slots we've // already allocated between transformations to avoid allocation and GC overhead during // transformation. - lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedVariableDeclarations; - lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = hoistedFunctionDeclarations; + lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentVariableDeclarations; + lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset] = lexicalEnvironmentFunctionDeclarations; lexicalEnvironmentStackOffset++; - hoistedVariableDeclarations = undefined; - hoistedFunctionDeclarations = undefined; + lexicalEnvironmentVariableDeclarations = undefined; + lexicalEnvironmentFunctionDeclarations = undefined; + } + + function suspendLexicalEnvironment(): void { + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); + lexicalEnvironmentSuspended = true; + } + + function resumeLexicalEnvironment(): void { + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment was not previously suspended."); + lexicalEnvironmentSuspended = false; } /** @@ -324,18 +267,19 @@ namespace ts { * any hoisted declarations added in this environment are returned. */ function endLexicalEnvironment(): Statement[] { - Debug.assert(!lexicalEnvironmentDisabled, "Cannot end a lexical environment during the print phase."); + Debug.assert(!scopeModificationDisabled, "Cannot end a lexical environment during the print phase."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is suspended."); let statements: Statement[]; - if (hoistedVariableDeclarations || hoistedFunctionDeclarations) { - if (hoistedFunctionDeclarations) { - statements = [...hoistedFunctionDeclarations]; + if (lexicalEnvironmentVariableDeclarations || lexicalEnvironmentFunctionDeclarations) { + if (lexicalEnvironmentFunctionDeclarations) { + statements = [...lexicalEnvironmentFunctionDeclarations]; } - if (hoistedVariableDeclarations) { + if (lexicalEnvironmentVariableDeclarations) { const statement = createVariableStatement( /*modifiers*/ undefined, - createVariableDeclarationList(hoistedVariableDeclarations) + createVariableDeclarationList(lexicalEnvironmentVariableDeclarations) ); if (!statements) { @@ -349,9 +293,38 @@ namespace ts { // Restore the previous lexical environment. lexicalEnvironmentStackOffset--; - hoistedVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; - hoistedFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + if (lexicalEnvironmentStackOffset === 0) { + lexicalEnvironmentVariableDeclarations = []; + lexicalEnvironmentFunctionDeclarations = []; + } return statements; } + + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); + if (helper.scoped) { + scopedEmitHelpers = append(scopedEmitHelpers, helper); + } + else { + nonScopedEmitHelpers = append(nonScopedEmitHelpers, helper); + } + } + + function readEmitHelpers(onlyScoped: boolean): EmitHelper[] { + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); + if (onlyScoped) { + const helpers = scopedEmitHelpers; + scopedEmitHelpers = undefined; + return helpers; + } + else { + const helpers = concatenate(nonScopedEmitHelpers, scopedEmitHelpers); + scopedEmitHelpers = undefined; + nonScopedEmitHelpers = undefined; + return helpers; + } + } } } diff --git a/src/compiler/transformers/destructuring.ts b/src/compiler/transformers/destructuring.ts index 837c6c57bb7..ba3f97f0d4d 100644 --- a/src/compiler/transformers/destructuring.ts +++ b/src/compiler/transformers/destructuring.ts @@ -42,10 +42,10 @@ namespace ts { * @param visitor An optional visitor used to visit default value initializers of binding patterns. */ export function flattenDestructuringToExpression( + context: TransformationContext, node: VariableDeclaration | DestructuringAssignment, needsValue: boolean, createAssignmentCallback: (target: Expression, value: Expression, location?: TextRange) => Expression, - recordTempVariable: (node: Identifier) => void, visitor?: (node: Node) => VisitResult): Expression { let location: TextRange = node; @@ -87,7 +87,16 @@ namespace ts { } } - flattenEffectiveBindingElement(node, value, isDestructuringAssignment(node), emitAssignment, emitTempVariableAssignment, visitor, location); + flattenEffectiveBindingElement( + context, + node, + value, + isDestructuringAssignment(node), + emitAssignment, + emitTempVariableAssignment, + emitExpression, + visitor, + location); if (value && needsValue) { expressions.push(value); @@ -98,22 +107,21 @@ namespace ts { function emitAssignment(target: Expression, value: Expression, location: TextRange, original: Node) { const expression = createAssignmentCallback(target, value, location); expression.original = original; - // NOTE: this completely disables source maps, but aligns with the behavior of - // `emitAssignment` in the old emitter. - setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); - aggregateTransformFlags(expression); - expressions.push(expression); + emitExpression(expression); } function emitTempVariableAssignment(value: Expression, location: TextRange) { - const name = createTempVariable(recordTempVariable); - const expression = createAssignment(name, value, location); + const name = createTempVariable(context.hoistVariableDeclaration); + emitExpression(createAssignment(name, value, location)); + return name; + } + + function emitExpression(expression: Expression) { // NOTE: this completely disables source maps, but aligns with the behavior of // `emitAssignment` in the old emitter. setEmitFlags(expression, EmitFlags.NoNestedSourceMaps); aggregateTransformFlags(expression); expressions.push(expression); - return name; } } @@ -127,14 +135,62 @@ namespace ts { * declaring them inline. */ export function flattenDestructuring( + context: TransformationContext, node: VariableDeclaration | ParameterDeclaration, - boundValue?: Expression, - recordTempVariable?: (node: Identifier) => void, + boundValue: Expression | undefined, + recordTempVariablesInLine: boolean, visitor?: (node: Node) => VisitResult) { - let pendingAssignments: Expression[]; + let pendingExpressions: Expression[]; + const pendingDeclarations: { pendingExpressions?: Expression[], name: Identifier, value: Expression, location?: TextRange, original?: Node }[] = []; const declarations: VariableDeclaration[] = []; - flattenEffectiveBindingElement(node, boundValue, /*skipInitializer*/ false, emitAssignment, emitTempVariableAssignment, visitor, /*location*/ node); + + flattenEffectiveBindingElement( + context, + node, + boundValue, + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + emitExpression, + visitor, + /*location*/ node); + + if (pendingExpressions) { + const name = createTempVariable(/*recordTempVariable*/ undefined); + if (recordTempVariablesInLine) { + pendingDeclarations.push({ name, value: inlineExpressions(pendingExpressions) }); + } + else { + context.hoistVariableDeclaration(name); + const pendingDeclaration = lastOrUndefined(pendingDeclarations); + pendingDeclaration.pendingExpressions = append( + pendingDeclaration.pendingExpressions, + createAssignment(name, pendingDeclaration.value) + ); + addRange(pendingDeclaration.pendingExpressions, pendingExpressions); + pendingDeclaration.value = name; + } + } + + for (const { pendingExpressions, name, value, location, original} of pendingDeclarations) { + const declaration = createVariableDeclaration( + name, + /*type*/ undefined, + // pendingExpressions + // ? inlineExpressions(append(pendingExpressions, value)) + // : value, + inlineExpressions(append(pendingExpressions, value)), + location + ); + + declaration.original = original; + + // NOTE: this completely disables source maps, but aligns with the behavior of + // `emitAssignment` in the old emitter. + declarations.push(aggregateTransformFlags(setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps))); + } + return declarations; function emitAssignment(name: Expression, value: Expression, location: TextRange, original: Node) { @@ -143,38 +199,35 @@ namespace ts { return; } - if (pendingAssignments) { - pendingAssignments.push(value); - value = inlineExpressions(pendingAssignments); - pendingAssignments = undefined; - } - - const declaration = createVariableDeclaration(name, /*type*/ undefined, value, location); - declaration.original = original; - - // NOTE: this completely disables source maps, but aligns with the behavior of - // `emitAssignment` in the old emitter. - declarations.push(aggregateTransformFlags(setEmitFlags(declaration, EmitFlags.NoNestedSourceMaps))); + pendingDeclarations.push({ pendingExpressions, name, value, location, original }); + pendingExpressions = undefined; } function emitTempVariableAssignment(value: Expression, location: TextRange) { - const name = createTempVariable(recordTempVariable); - if (recordTempVariable) { - pendingAssignments = append(pendingAssignments, createAssignment(name, value, location)); - } - else { + const name = createTempVariable(/*recordTempVariable*/ undefined); + if (recordTempVariablesInLine) { emitAssignment(name, value, location, /*original*/ undefined); } + else { + context.hoistVariableDeclaration(name); + emitExpression(createAssignment(name, value, location)); + } return name; } + + function emitExpression(expression: Expression) { + pendingExpressions = append(pendingExpressions, expression); + } } function flattenEffectiveBindingElement( + context: TransformationContext, bindingElement: EffectiveBindingElement, boundValue: Expression | undefined, skipInitializer: boolean, emitAssignment: (target: Expression, value: Expression, location: TextRange, original: Node) => void, emitTempVariableAssignment: (value: Expression, location: TextRange) => Identifier, + emitExpression: (value: Expression) => void, visitor: ((node: Node) => VisitResult) | undefined, location: TextRange) { @@ -194,19 +247,20 @@ namespace ts { if (isEffectiveBindingPattern(bindingTarget)) { const elements = getElementsOfEffectiveBindingPattern(bindingTarget); const numElements = elements.length; - if (numElements !== 1) { - // For anything other than a single-element destructuring we need to generate a temporary - // to ensure value is evaluated exactly once. Additionally, if we have zero elements - // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, - // so in that case, we'll intentionally create that temporary. - const reuseIdentifierExpressions = !isDeclarationBindingElement(bindingElement) || numElements !== 0; - boundValue = ensureIdentifier(boundValue, reuseIdentifierExpressions, emitTempVariableAssignment, location); - } - if (isEffectiveObjectBindingPattern(bindingTarget)) { + if (numElements !== 1) { + // For anything other than a single-element destructuring we need to generate a temporary + // to ensure value is evaluated exactly once. Additionally, if we have zero elements + // we need to emit *something* to ensure that in case a 'var' keyword was already emitted, + // so in that case, we'll intentionally create that temporary. + const reuseIdentifierExpressions = !isDeclarationBindingElement(bindingElement) || numElements !== 0; + boundValue = ensureIdentifier(boundValue, reuseIdentifierExpressions, emitTempVariableAssignment, location); + } + for (const element of elements) { // Rewrite element to a declaration with an initializer that fetches property flattenEffectiveBindingElement( + context, element, createDestructuringPropertyAccess( boundValue, @@ -215,35 +269,55 @@ namespace ts { /*skipInitializer*/ false, emitAssignment, emitTempVariableAssignment, + emitExpression, visitor, /*location*/ element); } } else { + if (!isArrayLiteralExpression(boundValue)) { + // Read the elements of the iterable into an array + boundValue = emitTempVariableAssignment( + createReadHelper( + context, + boundValue, + isEffectiveBindingElementWithRest(elements[numElements - 1]) + ? undefined + : numElements, + location + ), + location + ); + } + for (let i = 0; i < numElements; i++) { const element = elements[i]; - if (!isOmittedExpression(element)) { - if (!isEffectiveBindingElementWithRest(element)) { - // Rewrite element to a declaration that accesses array element at index i - flattenEffectiveBindingElement( - element, - createElementAccess(boundValue, i), - /*skipInitializer*/ false, - emitAssignment, - emitTempVariableAssignment, - visitor, - /*location*/ element); - } - else if (i === numElements - 1) { - flattenEffectiveBindingElement( - element, - createArraySlice(boundValue, i), - /*skipInitializer*/ false, - emitAssignment, - emitTempVariableAssignment, - visitor, - /*location*/ element); - } + if (isOmittedExpression(element)) { + continue; + } + else if (!isEffectiveBindingElementWithRest(element)) { + flattenEffectiveBindingElement( + context, + element, + createElementAccess(boundValue, i), + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + emitExpression, + visitor, + /*location*/ element); + } + else if (i === numElements - 1) { + flattenEffectiveBindingElement( + context, + element, + createArraySlice(boundValue, i), + /*skipInitializer*/ false, + emitAssignment, + emitTempVariableAssignment, + emitExpression, + visitor, + /*location*/ element); } } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 1e8ab4f971e..5b4971a9adc 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -166,11 +166,11 @@ namespace ts { export function transformES2015(context: TransformationContext) { const { startLexicalEnvironment, + resumeLexicalEnvironment, endLexicalEnvironment, - hoistVariableDeclaration, + hoistVariableDeclaration } = context; - const compilerOptions = context.getCompilerOptions(); const resolver = context.getEmitResolver(); const previousOnSubstituteNode = context.onSubstituteNode; const previousOnEmitNode = context.onEmitNode; @@ -187,7 +187,7 @@ namespace ts { let enclosingFunction: FunctionLikeDeclaration; let enclosingNonArrowFunction: FunctionLikeDeclaration; let enclosingNonAsyncFunctionBody: FunctionLikeDeclaration | ClassElement; - let helperState: EmitHelperState; + let enclosingLabeledStatements: LabeledStatement[]; /** * Used to track if we are emitting body of the converted loop @@ -210,15 +210,30 @@ namespace ts { currentSourceFile = node; currentText = node.text; - helperState = { currentSourceFile, compilerOptions }; + currentNode = node; + enclosingBlockScopeContainer = node; - const visited = visitNode(node, visitor, isSourceFile); - addEmitHelpers(visited, helperState.requestedHelpers); + let statements: Statement[] = []; + + startLexicalEnvironment(); + + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); + addCaptureThisForNodeIfNeeded(statements, node); + addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + addRange(statements, endLexicalEnvironment()); + + const updated = updateSourceFileNode( + node, + createNodeArray(statements, /*location*/ node.statements) + ); + + addEmitHelpers(updated, context.readEmitHelpers(/*onlyScoped*/ false)); currentSourceFile = undefined; currentText = undefined; - helperState = undefined; - return visited; + currentNode = undefined; + enclosingBlockScopeContainer = undefined; + return updated; } function visitor(node: Node): VisitResult { @@ -267,6 +282,47 @@ namespace ts { (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)); } + function onBeforeVisitNode(node: Node) { + if (currentNode) { + if (isBlockScope(currentNode, currentParent)) { + enclosingBlockScopeContainer = currentNode; + enclosingBlockScopeContainerParent = currentParent; + } + + if (isFunctionLike(currentNode)) { + enclosingFunction = currentNode; + if (currentNode.kind !== SyntaxKind.ArrowFunction) { + enclosingNonArrowFunction = currentNode; + if (!(getEmitFlags(currentNode) & EmitFlags.AsyncFunctionBody)) { + enclosingNonAsyncFunctionBody = currentNode; + } + } + } + + // keep track of the enclosing variable statement when in the context of + // variable statements, variable declarations, binding elements, and binding + // patterns. + switch (currentNode.kind) { + case SyntaxKind.VariableStatement: + enclosingVariableStatement = currentNode; + break; + + case SyntaxKind.VariableDeclarationList: + case SyntaxKind.VariableDeclaration: + case SyntaxKind.BindingElement: + case SyntaxKind.ObjectBindingPattern: + case SyntaxKind.ArrayBindingPattern: + break; + + default: + enclosingVariableStatement = undefined; + } + } + + currentParent = currentNode; + currentNode = node; + } + function visitorWorker(node: Node): VisitResult { if (shouldCheckNode(node)) { return visitJavaScript(node); @@ -418,9 +474,6 @@ namespace ts { case SyntaxKind.MethodDeclaration: return visitMethodDeclaration(node); - case SyntaxKind.SourceFile: - return visitSourceFileNode(node); - case SyntaxKind.VariableStatement: return visitVariableStatement(node); @@ -431,47 +484,6 @@ namespace ts { } - function onBeforeVisitNode(node: Node) { - if (currentNode) { - if (isBlockScope(currentNode, currentParent)) { - enclosingBlockScopeContainer = currentNode; - enclosingBlockScopeContainerParent = currentParent; - } - - if (isFunctionLike(currentNode)) { - enclosingFunction = currentNode; - if (currentNode.kind !== SyntaxKind.ArrowFunction) { - enclosingNonArrowFunction = currentNode; - if (!(getEmitFlags(currentNode) & EmitFlags.AsyncFunctionBody)) { - enclosingNonAsyncFunctionBody = currentNode; - } - } - } - - // keep track of the enclosing variable statement when in the context of - // variable statements, variable declarations, binding elements, and binding - // patterns. - switch (currentNode.kind) { - case SyntaxKind.VariableStatement: - enclosingVariableStatement = currentNode; - break; - - case SyntaxKind.VariableDeclarationList: - case SyntaxKind.VariableDeclaration: - case SyntaxKind.BindingElement: - case SyntaxKind.ObjectBindingPattern: - case SyntaxKind.ArrayBindingPattern: - break; - - default: - enclosingVariableStatement = undefined; - } - } - - currentParent = currentNode; - currentNode = node; - } - function visitSwitchStatement(node: SwitchStatement): SwitchStatement { Debug.assert(convertedLoopState !== undefined); @@ -769,7 +781,7 @@ namespace ts { if (extendsClauseElement) { statements.push( createStatement( - createExtendsHelper(helperState, getLocalName(node)), + createExtendsHelper(context, getLocalName(node)), /*location*/ extendsClauseElement ) ); @@ -819,11 +831,8 @@ namespace ts { // `super` call. // If this is the case, we do not include the synthetic `...args` parameter and // will instead use the `arguments` object in ES5/3. - if (constructor && !hasSynthesizedSuper) { - return visitNodes(constructor.parameters, visitor, isParameter); - } - - return []; + return visitParameterList(constructor && !hasSynthesizedSuper && constructor.parameters, visitor, context) + || []; } /** @@ -836,19 +845,19 @@ namespace ts { * synthesized `super` call. */ function transformConstructorBody(constructor: ConstructorDeclaration | undefined, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments, hasSynthesizedSuper: boolean) { - const statements: Statement[] = []; - startLexicalEnvironment(); + let statements: Statement[] = []; + resumeLexicalEnvironment(); let statementOffset = -1; if (hasSynthesizedSuper) { // If a super call has already been synthesized, // we're going to assume that we should just transform everything after that. // The assumption is that no prior step in the pipeline has added any prologue directives. - statementOffset = 1; + statementOffset = 0; } else if (constructor) { // Otherwise, try to emit all potential prologue directives first. - statementOffset = addPrologueDirectives(statements, constructor.body.statements, /*ensureUseStrict*/ false, visitor); + statementOffset = addPrologueDirectives(statements, constructor.body.statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); } if (constructor) { @@ -883,6 +892,7 @@ namespace ts { } addRange(statements, endLexicalEnvironment()); + const block = createBlock( createNodeArray( statements, @@ -1133,7 +1143,12 @@ namespace ts { createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList( - flattenDestructuring(parameter, temp, /*recordTempVariable*/ undefined, visitor) + flattenDestructuring( + context, + parameter, + temp, + /*recordTempVariablesInLine*/ true, + visitor) ) ), EmitFlags.CustomPrologue @@ -1363,22 +1378,17 @@ namespace ts { * @param member The MethodDeclaration node. */ function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) { + const commentRange = getCommentRange(member); const sourceMapRange = getSourceMapRange(member); - const func = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined); - setEmitFlags(func, EmitFlags.NoComments); - setSourceMapRange(func, sourceMapRange); + const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name); + const memberFunction = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined); + setEmitFlags(memberFunction, EmitFlags.NoComments); + setSourceMapRange(memberFunction, sourceMapRange); const statement = createStatement( - createAssignment( - createMemberAccessForPropertyName( - receiver, - visitNode(member.name, visitor, isPropertyName), - /*location*/ member.name - ), - func - ), + createAssignment(memberName, memberFunction), /*location*/ member ); @@ -1477,7 +1487,20 @@ namespace ts { enableSubstitutionsForCapturedThis(); } - const func = transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined); + const func = setOriginalNode( + createFunctionExpression( + /*modifiers*/ undefined, + node.asteriskToken, + /*name*/ undefined, + /*typeParameters*/ undefined, + visitParameterList(node.parameters, visitor, context), + /*type*/ undefined, + transformFunctionBody(node), + node + ), + /*original*/ node + ); + setEmitFlags(func, EmitFlags.CapturesThis); return func; } @@ -1488,7 +1511,17 @@ namespace ts { * @param node a FunctionExpression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - return transformFunctionLikeToExpression(node, /*location*/ node, node.name); + return updateFunctionExpression( + node, + /*modifiers*/ undefined, + node.name, + /*typeParameters*/ undefined, + visitParameterList(node.parameters, visitor, context), + /*type*/ undefined, + node.transformFlags & TransformFlags.ES2015 + ? transformFunctionBody(node) + : visitFunctionBody(node.body, visitor, context) + ); } /** @@ -1497,19 +1530,18 @@ namespace ts { * @param node a FunctionDeclaration node. */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - return setOriginalNode( - createFunctionDeclaration( - /*decorators*/ undefined, - node.modifiers, - node.asteriskToken, - node.name, - /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), - /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node - ), - /*original*/ node); + return updateFunctionDeclaration( + node, + /*decorators*/ undefined, + node.modifiers, + node.name, + /*typeParameters*/ undefined, + visitParameterList(node.parameters, visitor, context), + /*type*/ undefined, + node.transformFlags & TransformFlags.ES2015 + ? transformFunctionBody(node) + : visitFunctionBody(node.body, visitor, context) + ); } /** @@ -1531,7 +1563,7 @@ namespace ts { node.asteriskToken, name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, saveStateAndInvoke(node, transformFunctionBody), location @@ -1554,15 +1586,17 @@ namespace ts { let statementsLocation: TextRange; let closeBraceLocation: TextRange; - const statements: Statement[] = []; - const body = node.body; + let statements: Statement[] = []; let statementOffset: number; + const body = node.body; + + // Resume the lexical environment and iterator scope started in visitParameterList + resumeLexicalEnvironment(); - startLexicalEnvironment(); if (isBlock(body)) { // ensureUseStrict is false because no new prologue-directive should be added. // addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array - statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); + statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); } addCaptureThisForNodeIfNeeded(statements, node); @@ -1612,15 +1646,21 @@ namespace ts { closeBraceLocation = body; } - const lexicalEnvironment = endLexicalEnvironment(); - addRange(statements, lexicalEnvironment); + const declarations = endLexicalEnvironment(); + addRange(statements, declarations); // If we added any final generated statements, this must be a multi-line block - if (!multiLine && lexicalEnvironment && lexicalEnvironment.length) { + if (some(declarations)) { multiLine = true; } - const block = createBlock(createNodeArray(statements, statementsLocation), node.body, multiLine); + const block = createBlock( + createNodeArray(statements, statementsLocation), + node.body, + multiLine); + + setOriginalNode(block, node.body); + if (!multiLine && singleLine) { setEmitFlags(block, EmitFlags.SingleLine); } @@ -1629,7 +1669,6 @@ namespace ts { setTokenSourceMapRange(block, SyntaxKind.CloseBraceToken, closeBraceLocation); } - setOriginalNode(block, node.body); return block; } @@ -1638,7 +1677,7 @@ namespace ts { * * @param node An ExpressionStatement node. */ - function visitExpressionStatement(node: ExpressionStatement): ExpressionStatement { + function visitExpressionStatement(node: ExpressionStatement): Statement { // If we are here it is most likely because our expression is a destructuring assignment. switch (node.expression.kind) { case SyntaxKind.ParenthesizedExpression: @@ -1695,14 +1734,17 @@ namespace ts { */ function visitBinaryExpression(node: BinaryExpression, needsDestructuringValue: boolean): Expression { // If we are here it is because this is a destructuring assignment. - Debug.assert(isDestructuringAssignment(node)); - return flattenDestructuringToExpression( - node, - needsDestructuringValue, - createAssignment, - hoistVariableDeclaration, - visitor - ); + if (isDestructuringAssignment(node)) { + return flattenDestructuringToExpression( + context, + node, + needsDestructuringValue, + createAssignment, + visitor + ); + } + + return visitEachChild(node, visitor, context); } function visitVariableStatement(node: VariableStatement): Statement { @@ -1714,7 +1756,12 @@ namespace ts { if (decl.initializer) { let assignment: Expression; if (isBindingPattern(decl.name)) { - assignment = flattenDestructuringToExpression(decl, /*needsValue*/ false, createAssignment, hoistVariableDeclaration, visitor); + assignment = flattenDestructuringToExpression( + context, + decl, + /*needsValue*/ false, + createAssignment, + visitor); } else { assignment = createBinary(decl.name, SyntaxKind.EqualsToken, visitNode(decl.initializer, visitor, isExpression)); @@ -1870,9 +1917,10 @@ namespace ts { const recordTempVariablesInLine = !enclosingVariableStatement || !hasModifier(enclosingVariableStatement, ModifierFlags.Export); return flattenDestructuring( + context, node, /*value*/ undefined, - recordTempVariablesInLine ? undefined : hoistVariableDeclaration, + recordTempVariablesInLine, visitor); } @@ -1880,28 +1928,54 @@ namespace ts { } function visitLabeledStatement(node: LabeledStatement): VisitResult { + const enclosedStatement = getEnclosedStatement(node); if (convertedLoopState) { if (!convertedLoopState.labels) { convertedLoopState.labels = createMap(); } - convertedLoopState.labels[node.label.text] = node.label.text; + for (const labeledStatement of enclosedStatement.enclosingLabeledStatements) { + convertedLoopState.labels[labeledStatement.label.text] = labeledStatement.label.text; + } } let result: VisitResult; - if (isIterationStatement(node.statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node.statement)) { - result = visitNodes(createNodeArray([node.statement]), visitor, isStatement); + if (isIterationStatement(enclosedStatement.statement, /*lookInLabeledStatements*/ false)) { + enclosingLabeledStatements = enclosedStatement.enclosingLabeledStatements; + if (shouldConvertIterationStatementBody(enclosedStatement.statement)) { + result = visitNodes(createNodeArray([enclosedStatement.statement]), visitor, isStatement); + Debug.assert(enclosingLabeledStatements === undefined); + } + else { + result = restoreEnclosingLabels(visitNode(enclosedStatement.statement, visitor, isStatement), enclosingLabeledStatements); + enclosingLabeledStatements = undefined; + } } else { - result = visitEachChild(node, visitor, context); + result = restoreEnclosingLabels(visitNode(enclosedStatement.statement, visitor, isStatement), enclosedStatement.enclosingLabeledStatements); } if (convertedLoopState) { - convertedLoopState.labels[node.label.text] = undefined; + for (const labeledStatement of enclosedStatement.enclosingLabeledStatements) { + convertedLoopState.labels[labeledStatement.label.text] = undefined; + } } return result; } + function restoreEnclosingLabels(node: Statement, enclosingLabeledStatements: LabeledStatement[]) { + if (enclosingLabeledStatements) { + for (const labeledStatement of enclosingLabeledStatements) { + node = updateLabel( + labeledStatement, + labeledStatement.label, + node + ); + } + } + return node; + } + function visitDoStatement(node: DoStatement) { return convertIterationStatementBodyIfNecessary(node); } @@ -1924,65 +1998,29 @@ namespace ts { * @param node A ForOfStatement. */ function visitForOfStatement(node: ForOfStatement): VisitResult { - return convertIterationStatementBodyIfNecessary(node, convertForOfToFor); + return convertIterationStatementBodyIfNecessary(node, convertForOfStatement); } - function convertForOfToFor(node: ForOfStatement, convertedLoopBodyStatements: Statement[]): ForStatement { - // The following ES6 code: - // - // for (let v of expr) { } - // - // should be emitted as - // - // for (var _i = 0, _a = expr; _i < _a.length; _i++) { - // var v = _a[_i]; - // } - // - // where _a and _i are temps emitted to capture the RHS and the counter, - // respectively. - // When the left hand side is an expression instead of a let declaration, - // the "let v" is not emitted. - // When the left hand side is a let/const, the v is renamed if there is - // another v in scope. - // Note that all assignments to the LHS are emitted in the body, including - // all destructuring. - // Note also that because an extra statement is needed to assign to the LHS, - // for-of bodies are always emitted as blocks. - - const expression = visitNode(node.expression, visitor, isExpression); - const initializer = node.initializer; - const statements: Statement[] = []; - - // In the case where the user wrote an identifier as the RHS, like this: - // - // for (let v of arr) { } - // - // we don't want to emit a temporary variable for the RHS, just use it directly. - const counter = createLoopVariable(); - const rhsReference = expression.kind === SyntaxKind.Identifier - ? createUniqueName((expression).text) - : createTempVariable(/*recordTempVariable*/ undefined); - - // Initialize LHS - // var v = _a[_i]; - if (isVariableDeclarationList(initializer)) { - if (initializer.flags & NodeFlags.BlockScoped) { + function convertForOfStatementHead(statements: Statement[], node: ForOfStatement, boundValue: Expression, convertedLoopBodyStatements: Statement[]) { + if (isVariableDeclarationList(node.initializer)) { + if (node.initializer.flags & NodeFlags.BlockScoped) { enableSubstitutionsForBlockScopedBindings(); } - const firstOriginalDeclaration = firstOrUndefined(initializer.declarations); + const firstOriginalDeclaration = firstOrUndefined(node.initializer.declarations); if (firstOriginalDeclaration && isBindingPattern(firstOriginalDeclaration.name)) { // This works whether the declaration is a var, let, or const. // It will use rhsIterationValue _a[_i] as the initializer. const declarations = flattenDestructuring( + context, firstOriginalDeclaration, - createElementAccess(rhsReference, counter), - /*recordTempVariable*/ undefined, + boundValue, + /*recordTempVariablesInLine*/ true, visitor ); - const declarationList = createVariableDeclarationList(declarations, /*location*/ initializer); - setOriginalNode(declarationList, initializer); + const declarationList = createVariableDeclarationList(declarations, /*location*/ node.initializer); + setOriginalNode(declarationList, node.initializer); // Adjust the source map range for the first declaration to align with the old // emitter. @@ -2004,16 +2042,19 @@ namespace ts { createVariableStatement( /*modifiers*/ undefined, setOriginalNode( - createVariableDeclarationList([ - createVariableDeclaration( - firstOriginalDeclaration ? firstOriginalDeclaration.name : createTempVariable(/*recordTempVariable*/ undefined), - /*type*/ undefined, - createElementAccess(rhsReference, counter) - ) - ], /*location*/ moveRangePos(initializer, -1)), - initializer + createVariableDeclarationList( + [ + createVariableDeclaration( + firstOriginalDeclaration ? firstOriginalDeclaration.name : createTempVariable(/*recordTempVariable*/ undefined), + /*type*/ undefined, + boundValue + ) + ], + /*location*/ moveRangePos(node.initializer, -1) + ), + node.initializer ), - /*location*/ moveRangeEnd(initializer, -1) + /*location*/ moveRangeEnd(node.initializer, -1) ) ); } @@ -2021,26 +2062,13 @@ namespace ts { else { // Initializer is an expression. Emit the expression in the body, so that it's // evaluated on every iteration. - const assignment = createAssignment(initializer, createElementAccess(rhsReference, counter)); + const assignment = createAssignment(node.initializer, boundValue); if (isDestructuringAssignment(assignment)) { - // This is a destructuring pattern, so we flatten the destructuring instead. - statements.push( - createStatement( - flattenDestructuringToExpression( - assignment, - /*needsValue*/ false, - createAssignment, - hoistVariableDeclaration, - visitor - ) - ) - ); + statements.push(visitNode(createStatement(assignment), visitor, isStatement)); } else { - // Currently there is not way to check that assignment is binary expression of destructing assignment - // so we have to cast never type to binaryExpression - (assignment).end = initializer.end; - statements.push(createStatement(assignment, /*location*/ moveRangeEnd(initializer, -1))); + assignment.end = node.initializer.end; + statements.push(createStatement(visitNode(assignment, visitor, isExpression), /*location*/ moveRangeEnd(node.initializer, -1))); } } @@ -2061,6 +2089,118 @@ namespace ts { } } + return { bodyLocation, statementsLocation }; + } + + function convertForOfStatement(node: ForOfStatement, outerEnclosingLabeledStatements: LabeledStatement[], convertedLoopBodyStatements: Statement[]): Statement { + const expression = visitNode(node.expression, visitor, isExpression); + if (isArrayLiteralExpression(expression)) { + return convertForOfStatementForArrayLiteral(node, outerEnclosingLabeledStatements, convertedLoopBodyStatements, expression); + } + + const iteratorRecord = isIdentifier(node.expression) + ? getGeneratedNameForNode(node.expression) + : createUniqueName("iterator"); + + const statements: Statement[] = []; + const { bodyLocation, statementsLocation } = convertForOfStatementHead( + statements, + node, + createPropertyAccess( + createPropertyAccess(iteratorRecord, "result"), + "value" + ), + convertedLoopBodyStatements); + + let statement: Statement = setEmitFlags( + createFor( + createVariableDeclarationList( + [ + createVariableDeclaration( + iteratorRecord, + /*type*/ undefined, + createObjectLiteral( + [ + createPropertyAssignment( + "iterator", + createValuesHelper( + context, + expression, + node.expression + ), + node.expression + ) + ], + node.expression + ), + node.expression + ) + ], + node.expression + ), + /*condition*/ createStepHelper( + context, + iteratorRecord, + node.initializer + ), + /*incrementor*/ undefined, + setEmitFlags( + createBlock( + createNodeArray(statements, statementsLocation), + bodyLocation, + /*multiLine*/ true + ), + EmitFlags.NoSourceMap | EmitFlags.NoTokenSourceMaps + ), + /*location*/ node + ), + EmitFlags.NoTokenTrailingSourceMaps + ); + + statement = restoreEnclosingLabels(statement, outerEnclosingLabeledStatements); + return closeIterator(statement, iteratorRecord); + } + + function convertForOfStatementForArrayLiteral(node: ForOfStatement, outerEnclosingLabeledStatements: LabeledStatement[], convertedLoopBodyStatements: Statement[], expression: Expression): Statement { + // The following ES6 code: + // + // for (let v of expr) { } + // + // should be emitted as + // + // for (var _i = 0, _a = expr; _i < _a.length; _i++) { + // var v = _a[_i]; + // } + // + // where _a and _i are temps emitted to capture the RHS and the counter, + // respectively. + // When the left hand side is an expression instead of a let declaration, + // the "let v" is not emitted. + // When the left hand side is a let/const, the v is renamed if there is + // another v in scope. + // Note that all assignments to the LHS are emitted in the body, including + // all destructuring. + // Note also that because an extra statement is needed to assign to the LHS, + // for-of bodies are always emitted as blocks. + + const statements: Statement[] = []; + + // In the case where the user wrote an identifier as the RHS, like this: + // + // for (let v of arr) { } + // + // we don't want to emit a temporary variable for the RHS, just use it directly. + const counter = createLoopVariable(); + const rhsReference = expression.kind === SyntaxKind.Identifier + ? createUniqueName((expression).text) + : createTempVariable(/*recordTempVariable*/ undefined); + + const { bodyLocation, statementsLocation } = convertForOfStatementHead( + statements, + node, + createElementAccess(rhsReference, counter), + convertedLoopBodyStatements); + // The old emitter does not emit source maps for the expression setEmitFlags(expression, EmitFlags.NoSourceMap | getEmitFlags(expression)); @@ -2093,7 +2233,73 @@ namespace ts { // Disable trailing source maps for the OpenParenToken to align source map emit with the old emitter. setEmitFlags(forStatement, EmitFlags.NoTokenTrailingSourceMaps); - return forStatement; + return restoreEnclosingLabels(forStatement, outerEnclosingLabeledStatements); + } + + function closeIterator(statement: Statement, iteratorRecord: Expression) { + const errorRecord = createUniqueName("e"); + hoistVariableDeclaration(errorRecord); + const catchVariable = getGeneratedNameForNode(errorRecord); + + return createTry( + createBlock([ + statement + ]), + createCatchClause(catchVariable, + setEmitFlags( + createBlock([ + createStatement( + createAssignment( + errorRecord, + createObjectLiteral([ + createPropertyAssignment( + "error", + catchVariable + ) + ]) + ) + ) + ]), + EmitFlags.SingleLine + ) + ), + createBlock([ + setEmitFlags( + createTry( + setEmitFlags( + createBlock([ + createStatement( + createCloseHelper( + context, + iteratorRecord + ) + ) + ]), + EmitFlags.SingleLine + ), + undefined, + setEmitFlags( + createBlock([ + setEmitFlags( + createIf( + errorRecord, + createThrow( + createPropertyAccess( + errorRecord, + "error" + ) + ) + ), + EmitFlags.SingleLine + ) + ]), + EmitFlags.SingleLine + ) + ), + EmitFlags.SingleLine + ) + ]) + ); } /** @@ -2178,7 +2384,10 @@ namespace ts { } } - function convertIterationStatementBodyIfNecessary(node: IterationStatement, convert?: (node: IterationStatement, convertedLoopBodyStatements: Statement[]) => IterationStatement): VisitResult { + function convertIterationStatementBodyIfNecessary(node: IterationStatement, convert?: (node: IterationStatement, enclosingLabeledStatements: LabeledStatement[], convertedLoopBodyStatements: Statement[]) => Statement): VisitResult { + const outerEnclosingLabeledStatements = enclosingLabeledStatements; + enclosingLabeledStatements = undefined; + if (!shouldConvertIterationStatementBody(node)) { let saveAllowedNonLabeledJumps: Jump; if (convertedLoopState) { @@ -2188,11 +2397,18 @@ namespace ts { convertedLoopState.allowedNonLabeledJumps = Jump.Break | Jump.Continue; } - const result = convert ? convert(node, /*convertedLoopBodyStatements*/ undefined) : visitEachChild(node, visitor, context); + let result: Statement; + if (convert) { + result = convert(node, outerEnclosingLabeledStatements, /*convertedLoopBodyStatements*/ undefined); + } + else { + result = restoreEnclosingLabels(visitEachChild(node, visitor, context), outerEnclosingLabeledStatements); + } if (convertedLoopState) { convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps; } + return result; } @@ -2376,18 +2592,19 @@ namespace ts { } const convertedLoopBodyStatements = generateCallToConvertedLoop(functionName, loopParameters, currentState, isAsyncBlockContainingAwait); - let loop: IterationStatement; + + let loop: Statement; if (convert) { - loop = convert(node, convertedLoopBodyStatements); + loop = convert(node, outerEnclosingLabeledStatements, convertedLoopBodyStatements); } else { - loop = getMutableClone(node); + loop = getMutableClone(node); // clean statement part - loop.statement = undefined; + (loop).statement = undefined; // visit childnodes to transform initializer/condition/incrementor parts loop = visitEachChild(loop, visitor, context); // set loop statement - loop.statement = createBlock( + (loop).statement = createBlock( convertedLoopBodyStatements, /*location*/ undefined, /*multiline*/ true @@ -2395,15 +2612,11 @@ namespace ts { // reset and re-aggregate the transform flags loop.transformFlags = 0; + loop = restoreEnclosingLabels(loop, outerEnclosingLabeledStatements); aggregateTransformFlags(loop); } - - statements.push( - currentParent.kind === SyntaxKind.LabeledStatement - ? createLabel((currentParent).label, loop) - : loop - ); + statements.push(loop); return statements; } @@ -2421,6 +2634,8 @@ namespace ts { function generateCallToConvertedLoop(loopFunctionExpressionName: Identifier, parameters: ParameterDeclaration[], state: ConvertedLoopState, isAsyncBlockContainingAwait: boolean): Statement[] { const outerConvertedLoopState = convertedLoopState; + const savedEnclosingLabeledStatements = enclosingLabeledStatements; + enclosingLabeledStatements = undefined; const statements: Statement[] = []; // loop is considered simple if it does not have any return statements or break\continue that transfer control outside of the loop @@ -2494,6 +2709,8 @@ namespace ts { ); } } + + enclosingLabeledStatements = savedEnclosingLabeledStatements; return statements; } @@ -2711,7 +2928,7 @@ namespace ts { */ function visitArrayLiteralExpression(node: ArrayLiteralExpression): Expression { // We are here because we contain a SpreadElementExpression. - return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, node.multiLine, /*hasTrailingComma*/ node.elements.hasTrailingComma); + return transformAndSpreadElements(node.elements, node.multiLine, /*hasTrailingComma*/ node.elements.hasTrailingComma); } /** @@ -2754,7 +2971,7 @@ namespace ts { resultingCall = createFunctionApply( visitNode(target, visitor, isExpression), visitNode(thisArg, visitor, isExpression), - transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) + transformAndSpreadElements(node.arguments, /*multiLine*/ false, /*hasTrailingComma*/ false) ); } else { @@ -2811,7 +3028,7 @@ namespace ts { createFunctionApply( visitNode(target, visitor, isExpression), thisArg, - transformAndSpreadElements(createNodeArray([createVoidZero(), ...node.arguments]), /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) + transformAndSpreadElements(createNodeArray([createVoidZero(), ...node.arguments]), /*multiLine*/ false, /*hasTrailingComma*/ false) ), /*typeArguments*/ undefined, [] @@ -2825,7 +3042,7 @@ namespace ts { * @param needsUniqueCopy A value indicating whether to ensure that the result is a fresh array. * @param multiLine A value indicating whether the result should be emitted on multiple lines. */ - function transformAndSpreadElements(elements: NodeArray, needsUniqueCopy: boolean, multiLine: boolean, hasTrailingComma: boolean): Expression { + function transformAndSpreadElements(elements: NodeArray, multiLine: boolean, hasTrailingComma: boolean): Expression { // [source] // [a, ...b, c] // @@ -2842,14 +3059,16 @@ namespace ts { ); if (segments.length === 1) { - const firstElement = elements[0]; - return needsUniqueCopy && isSpreadElementExpression(firstElement) && firstElement.expression.kind !== SyntaxKind.ArrayLiteralExpression - ? createArraySlice(segments[0]) - : segments[0]; + const firstSegment = segments[0]; + if (isCallExpression(firstSegment) + && isIdentifier(firstSegment.expression) + && (getEmitFlags(firstSegment.expression) & EmitFlags.HelperName) + && firstSegment.expression.text === "___spread") { + return segments[0]; + } } - // Rewrite using the pattern .concat(, , ...) - return createArrayConcat(segments.shift(), segments); + return createSpreadHelper(context, segments); } function partitionSpreadElement(node: Expression) { @@ -3056,19 +3275,6 @@ namespace ts { : createIdentifier("_super"); } - function visitSourceFileNode(node: SourceFile): SourceFile { - const [prologue, remaining] = span(node.statements, isPrologueDirective); - const statements: Statement[] = []; - startLexicalEnvironment(); - addRange(statements, prologue); - addCaptureThisForNodeIfNeeded(statements, node); - addRange(statements, visitNodes(createNodeArray(remaining), visitor, isStatement)); - addRange(statements, endLexicalEnvironment()); - const clone = getMutableClone(node); - clone.statements = createNodeArray(statements, /*location*/ node.statements); - return clone; - } - /** * Called by the printer just before a node is printed. * @@ -3230,8 +3436,7 @@ namespace ts { return false; } - const parameter = singleOrUndefined(constructor.parameters); - if (!parameter || !nodeIsSynthesized(parameter) || !parameter.dotDotDotToken) { + if (some(constructor.parameters)) { return false; } @@ -3256,14 +3461,14 @@ namespace ts { } const expression = (callArgument).expression; - return isIdentifier(expression) && expression === parameter.name; + return isIdentifier(expression) && expression.text === "arguments"; } } - function createExtendsHelper(helperState: EmitHelperState, name: Identifier) { - requestEmitHelper(helperState, extendsHelper); + function createExtendsHelper(context: TransformationContext, name: Identifier) { + context.requestEmitHelper(extendsHelper); return createCall( - getHelperName(helperState, "__extends"), + getHelperName("__extends"), /*typeArguments*/ undefined, [ name, diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index 14d97ade2ac..581b67dfabf 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -13,7 +13,8 @@ namespace ts { export function transformES2017(context: TransformationContext) { const { startLexicalEnvironment, - endLexicalEnvironment, + resumeLexicalEnvironment, + endLexicalEnvironment } = context; const resolver = context.getEmitResolver(); @@ -22,7 +23,6 @@ namespace ts { // These variables contain state that changes as we descend into the tree. let currentSourceFile: SourceFile; - let helperState: EmitHelperState; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -50,8 +50,6 @@ namespace ts { context.onEmitNode = onEmitNode; context.onSubstituteNode = onSubstituteNode; - let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; - return transformSourceFile; function transformSourceFile(node: SourceFile) { @@ -60,14 +58,11 @@ namespace ts { } currentSourceFile = node; - helperState = { currentSourceFile, compilerOptions }; const visited = visitEachChild(node, visitor, context); - - addEmitHelpers(visited, helperState.requestedHelpers); + addEmitHelpers(visited, context.readEmitHelpers(/*onlyScoped*/ false)); currentSourceFile = undefined; - helperState = undefined; return visited; } @@ -148,7 +143,7 @@ namespace ts { node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, transformFunctionBody(node), /*location*/ node @@ -176,7 +171,7 @@ namespace ts { node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, transformFunctionBody(node), /*location*/ node @@ -205,7 +200,7 @@ namespace ts { node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, transformFunctionBody(node), /*location*/ node @@ -228,10 +223,10 @@ namespace ts { const func = createArrowFunction( visitNodes(node.modifiers, visitor, isModifier), /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, node.equalsGreaterThanToken, - transformConciseBody(node), + transformFunctionBody(node), /*location*/ node ); @@ -239,27 +234,9 @@ namespace ts { return func; } - function transformFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody { - return transformAsyncFunctionBody(node); - } - - function transformConciseBody(node: ArrowFunction): ConciseBody { - return transformAsyncFunctionBody(node); - } - - function transformFunctionBodyWorker(body: Block, start = 0) { - const savedCurrentScope = currentScope; - currentScope = body; - startLexicalEnvironment(); - - const statements = visitNodes(body.statements, visitor, isStatement, start); - const visited = updateBlock(body, statements); - const declarations = endLexicalEnvironment(); - currentScope = savedCurrentScope; - return mergeFunctionBodyLexicalEnvironment(visited, declarations); - } - - function transformAsyncFunctionBody(node: FunctionLikeDeclaration): ConciseBody | FunctionBody { + function transformFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody; + function transformFunctionBody(node: ArrowFunction): ConciseBody; + function transformFunctionBody(node: FunctionLikeDeclaration): ConciseBody | FunctionBody { const nodeType = node.original ? (node.original).type : node.type; const promiseConstructor = languageVersion < ScriptTarget.ES2015 ? getPromiseConstructor(nodeType) : undefined; const isArrowFunction = node.kind === SyntaxKind.ArrowFunction; @@ -271,14 +248,15 @@ namespace ts { // passed to `__awaiter` is executed inside of the callback to the // promise constructor. + resumeLexicalEnvironment(); if (!isArrowFunction) { - const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, (node.body).statements, /*ensureUseStrict*/ false, visitor); + let statements: Statement[] = []; + const statementOffset = addPrologueDirectives(statements, (node.body).statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); statements.push( createReturn( createAwaiterHelper( - helperState, + context, hasLexicalArguments, promiseConstructor, transformFunctionBodyWorker(node.body, statementOffset) @@ -286,6 +264,8 @@ namespace ts { ) ); + addRange(statements, endLexicalEnvironment()); + const block = createBlock(statements, /*location*/ node.body, /*multiLine*/ true); // Minor optimization, emit `_super` helper to capture `super` access in an arrow. @@ -304,32 +284,36 @@ namespace ts { return block; } else { - return createAwaiterHelper( - helperState, + const expression = createAwaiterHelper( + context, hasLexicalArguments, promiseConstructor, - transformConciseBodyWorker(node.body, /*forceBlockFunctionBody*/ true) + transformFunctionBodyWorker(node.body) ); + + const declarations = endLexicalEnvironment(); + if (some(declarations)) { + const block = toFunctionBody(expression); + const statements = mergeLexicalEnvironment(block.statements, declarations); + return updateBlock(block, statements); + } + + return expression; } } - function transformConciseBodyWorker(body: Block | Expression, forceBlockFunctionBody: boolean) { + function transformFunctionBodyWorker(body: ConciseBody, start = 0) { if (isBlock(body)) { - return transformFunctionBodyWorker(body); + return updateBlock( + body, + visitLexicalEnvironment(body.statements, visitor, context, start) + ); } else { startLexicalEnvironment(); - const visited: Expression | Block = visitNode(body, visitor, isConciseBody); - const declarations = endLexicalEnvironment(); - const merged = mergeFunctionBodyLexicalEnvironment(visited, declarations); - if (forceBlockFunctionBody && !isBlock(merged)) { - return createBlock([ - createReturn(merged) - ]); - } - else { - return merged; - } + const visited = toFunctionBody(visitNode(body, visitor, isConciseBody)); + const statements = mergeLexicalEnvironment(visited.statements, endLexicalEnvironment()); + return updateBlock(visited, statements); } } @@ -506,7 +490,9 @@ namespace ts { } } - function createAwaiterHelper(helperState: EmitHelperState, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + function createAwaiterHelper(context: TransformationContext, hasLexicalArguments: boolean, promiseConstructor: EntityName | Expression, body: Block) { + context.requestEmitHelper(awaiterHelper); + const generatorFunc = createFunctionExpression( /*modifiers*/ undefined, createToken(SyntaxKind.AsteriskToken), @@ -520,9 +506,8 @@ namespace ts { // Mark this node as originally an async function (generatorFunc.emitNode || (generatorFunc.emitNode = {})).flags |= EmitFlags.AsyncFunctionBody; - requestEmitHelper(helperState, awaiterHelper); return createCall( - getHelperName(helperState, "__awaiter"), + getHelperName("__awaiter"), /*typeArguments*/ undefined, [ createThis(), diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 78c9e42c1bf..320eb48205c 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -231,6 +231,7 @@ namespace ts { endLexicalEnvironment, hoistFunctionDeclaration, hoistVariableDeclaration, + readEmitHelpers } = context; const compilerOptions = context.getCompilerOptions(); @@ -242,7 +243,6 @@ namespace ts { let currentSourceFile: SourceFile; let renamedCatchVariables: Map; let renamedCatchVariableDeclarations: Map; - let helperState: EmitHelperState; let inGeneratorFunctionBody: boolean; let inStatementContainingYield: boolean; @@ -298,13 +298,11 @@ namespace ts { } currentSourceFile = node; - helperState = { currentSourceFile, compilerOptions }; const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, helperState.requestedHelpers); + addEmitHelpers(visited, readEmitHelpers(/*onlyScoped*/ false)); currentSourceFile = undefined; - helperState = undefined; return visited; } @@ -449,7 +447,7 @@ namespace ts { */ function visitFunctionDeclaration(node: FunctionDeclaration): Statement { // Currently, we only support generators that were originally async functions. - if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { node = setOriginalNode( createFunctionDeclaration( /*decorators*/ undefined, @@ -497,7 +495,7 @@ namespace ts { */ function visitFunctionExpression(node: FunctionExpression): Expression { // Currently, we only support generators that were originally async functions. - if (node.asteriskToken && getEmitFlags(node) & EmitFlags.AsyncFunctionBody) { + if (node.asteriskToken) { node = setOriginalNode( createFunctionExpression( /*modifiers*/ undefined, @@ -584,7 +582,7 @@ namespace ts { // Build the generator startLexicalEnvironment(); - const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, visitor); + const statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); transformAndEmitStatements(body.statements, statementOffset); @@ -934,7 +932,7 @@ namespace ts { const resumeLabel = defineLabel(); const expression = visitNode(node.expression, visitor, isExpression); if (node.asteriskToken) { - emitYieldStar(expression, /*location*/ node); + emitYieldStar(createValuesHelper(context, expression, /*location*/ node), /*location*/ node); } else { emitYield(expression, /*location*/ node); @@ -2590,7 +2588,7 @@ namespace ts { const buildResult = buildStatements(); return createGeneratorHelper( - helperState, + context, setEmitFlags( createFunctionExpression( /*modifiers*/ undefined, @@ -3083,10 +3081,10 @@ namespace ts { } } - function createGeneratorHelper(helperState: EmitHelperState, body: FunctionExpression) { - requestEmitHelper(helperState, generatorHelper); + function createGeneratorHelper(context: TransformationContext, body: FunctionExpression) { + context.requestEmitHelper(generatorHelper); return createCall( - getHelperName(helperState, "__generator"), + getHelperName("__generator"), /*typeArguments*/ undefined, [createThis(), body]); } @@ -3157,7 +3155,7 @@ namespace ts { text: ` var __generator = (this && this.__generator) || function (thisArg, body) { var _ = { label: 0, sent: function() { if (t[0] & 1) throw t[1]; return t[1]; }, trys: [], ops: [] }, f, y, t; - return { next: verb(0), "throw": verb(1), "return": verb(2) }; + return { next: verb(0), "throw": verb(1), "return": verb(2), __iterator__: function () { return this; } }; function verb(n) { return function (v) { return step([n, v]); }; } function step(op) { if (f) throw new TypeError("Generator is already executing."); diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 8867896e51e..5d847b82be9 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -5,8 +5,6 @@ namespace ts { export function transformJsx(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); - let currentSourceFile: SourceFile; - let helperState: EmitHelperState; return transformSourceFile; @@ -20,14 +18,8 @@ namespace ts { return node; } - currentSourceFile = node; - helperState = { currentSourceFile, compilerOptions }; - const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, helperState.requestedHelpers); - - currentSourceFile = undefined; - helperState = undefined; + addEmitHelpers(visited, context.readEmitHelpers(/*onlyScoped*/ false)); return visited; } @@ -116,7 +108,7 @@ namespace ts { // a call to the __assign helper. objectProperties = singleOrUndefined(segments); if (!objectProperties) { - objectProperties = createAssignHelper(helperState, segments); + objectProperties = createAssignHelper(context, segments); } } @@ -538,10 +530,10 @@ namespace ts { "diams": 0x2666 }); - function createAssignHelper(helperState: EmitHelperState, attributesSegments: Expression[]) { - requestEmitHelper(helperState, assignHelper); + function createAssignHelper(context: TransformationContext, attributesSegments: Expression[]) { + context.requestEmitHelper(assignHelper); return createCall( - getHelperName(helperState, "__assign"), + getHelperName("__assign"), /*typeArguments*/ undefined, attributesSegments ); diff --git a/src/compiler/transformers/module/es2015.ts b/src/compiler/transformers/module/es2015.ts index daef9cb5a4d..73cdaf12b39 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -5,6 +5,13 @@ namespace ts { export function transformES2015Module(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); + const previousOnEmitNode = context.onEmitNode; + const previousOnSubstituteNode = context.onSubstituteNode; + context.onEmitNode = onEmitNode; + context.onSubstituteNode = onSubstituteNode; + context.enableSubstitution(SyntaxKind.SourceFile); + + let currentSourceFile: SourceFile; return transformSourceFile; function transformSourceFile(node: SourceFile) { @@ -55,5 +62,56 @@ namespace ts { // Elide `export=` as it is not legal with --module ES6 return node.isExportEquals ? undefined : node; } + + // + // Emit Notification + // + + /** + * Hook for node emit notifications. + * + * @param emitContext A context hint for the emitter. + * @param node The node to emit. + * @param emit A callback used to emit the node in the printer. + */ + function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { + if (isSourceFile(node)) { + currentSourceFile = node; + previousOnEmitNode(emitContext, node, emitCallback); + currentSourceFile = undefined; + } + else { + previousOnEmitNode(emitContext, node, emitCallback); + } + } + + // + // Substitutions + // + + /** + * Hooks node substitutions. + * + * @param emitContext A context hint for the emitter. + * @param node The node to substitute. + */ + function onSubstituteNode(emitContext: EmitContext, node: Node) { + node = previousOnSubstituteNode(emitContext, node); + if (isIdentifier(node) && emitContext === EmitContext.Expression) { + return substituteExpressionIdentifier(node); + } + + return node; + } + + function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getOrCreateExternalHelpersModuleName(currentSourceFile, compilerOptions); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + } + return node; + } } } diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index b3c2178e99c..1e3cc9ae904 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -21,7 +21,6 @@ namespace ts { const { startLexicalEnvironment, endLexicalEnvironment, - hoistVariableDeclaration, } = context; const compilerOptions = context.getCompilerOptions(); @@ -46,7 +45,6 @@ namespace ts { let currentSourceFile: SourceFile; // The current file. let currentModuleInfo: ExternalModuleInfo; // The ExternalModuleInfo for the current file. let noSubstitution: Map; // Set of nodes for which substitution rules should be ignored. - let helperState: EmitHelperState; return transformSourceFile; @@ -64,15 +62,14 @@ namespace ts { currentSourceFile = node; currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); - helperState = { currentSourceFile, compilerOptions }; // Perform the transformation. const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; const updated = transformModule(node); + addEmitHelpers(updated, context.readEmitHelpers(/*onlyScoped*/ false)); currentSourceFile = undefined; currentModuleInfo = undefined; - helperState = undefined; return aggregateTransformFlags(updated); } @@ -84,15 +81,17 @@ namespace ts { function transformCommonJSModule(node: SourceFile) { startLexicalEnvironment(); - const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); + let statements: Statement[] = []; + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, /*ignoreCustomPrologue*/ false, sourceElementVisitor); append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); - addRange(statements, endLexicalEnvironment()); addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); + addRange(statements, endLexicalEnvironment()); const updated = updateSourceFileNode(node, createNodeArray(statements, node.statements)); if (currentModuleInfo.hasExportStarsToExportValues) { + // If we have any `export * from ...` declarations + // we need to inform the emitter to add the __export helper. addEmitHelper(updated, exportStarHelper); } @@ -258,20 +257,20 @@ namespace ts { function transformAsynchronousModuleBody(node: SourceFile) { startLexicalEnvironment(); - const statements: Statement[] = []; - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); + let statements: Statement[] = []; + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, /*ignoreCustomPrologue*/ false, sourceElementVisitor); // Visit each statement of the module body. append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement, /*optional*/ true)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); + // Append the 'export =' statement if provided. + addExportEqualsIfNeeded(statements, /*emitAsReturn*/ true); + // End the lexical environment for the module body // and merge any new lexical declarations. addRange(statements, endLexicalEnvironment()); - // Append the 'export =' statement if provided. - addExportEqualsIfNeeded(statements, /*emitAsReturn*/ true); - const body = createBlock(statements, /*location*/ undefined, /*multiLine*/ true); if (currentModuleInfo.hasExportStarsToExportValues) { // If we have any `export * from ...` declarations @@ -764,10 +763,10 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration): Expression { if (isBindingPattern(node.name)) { return flattenDestructuringToExpression( + context, node, /*needsValue*/ false, createExportExpression, - hoistVariableDeclaration, /*visitor*/ undefined ); } @@ -1194,6 +1193,15 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getOrCreateExternalHelpersModuleName(currentSourceFile, compilerOptions); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + + return node; + } + if (!isGeneratedIdentifier(node) && !isLocalName(node)) { const exportContainer = resolver.getReferencedExportContainer(node, isExportName(node)); if (exportContainer && exportContainer.kind === SyntaxKind.SourceFile) { diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index f6d051d25ef..9e5a9f0f993 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -41,7 +41,6 @@ namespace ts { let hoistedStatements: Statement[]; let enclosingBlockScopedContainer: Node; let noSubstitution: Map; // Set of nodes for which substitution rules should be ignored. - let helperState: EmitHelperState; return transformSourceFile; @@ -60,7 +59,6 @@ namespace ts { const id = getOriginalNodeId(node); currentSourceFile = node; enclosingBlockScopedContainer = node; - helperState = { currentSourceFile, compilerOptions }; // System modules have the following shape: // @@ -121,6 +119,10 @@ namespace ts { if (!(compilerOptions.outFile || compilerOptions.out)) { moveEmitHelpers(updated, moduleBodyBlock, helper => !helper.scoped); + addEmitHelpers(moduleBodyBlock, context.readEmitHelpers(/*onlyScoped*/ false)); + } + else { + addEmitHelpers(updated, context.readEmitHelpers(/*onlyScoped*/ false)); } if (noSubstitution) { @@ -134,8 +136,6 @@ namespace ts { contextObject = undefined; hoistedStatements = undefined; enclosingBlockScopedContainer = undefined; - helperState = undefined; - return aggregateTransformFlags(updated); } @@ -224,7 +224,7 @@ namespace ts { startLexicalEnvironment(); // Add any prologue directives. - const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, sourceElementVisitor); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ !compilerOptions.noImplicitUseStrict, /*ignoreCustomPrologue*/ false, sourceElementVisitor); // var __moduleName = context_1 && context_1.id; statements.push( @@ -822,7 +822,12 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration, isExportedDeclaration: boolean): Expression { const createAssignment = isExportedDeclaration ? createExportedVariableAssignment : createNonExportedVariableAssignment; return isBindingPattern(node.name) - ? flattenDestructuringToExpression(node, /*needsValue*/ false, createAssignment, hoistVariableDeclaration, destructuringVisitor) + ? flattenDestructuringToExpression( + context, + node, + /*needsValue*/ false, + createAssignment, + destructuringVisitor) : createAssignment(node.name, visitNode(node.initializer, destructuringVisitor, isExpression)); } @@ -1473,7 +1478,12 @@ namespace ts { */ function visitDestructuringAssignment(node: DestructuringAssignment): VisitResult { if (hasExportedReferenceInDestructuringTarget(node.left)) { - return flattenDestructuringToExpression(node, /*needsValue*/ true, createAssignment, hoistVariableDeclaration, destructuringVisitor); + return flattenDestructuringToExpression( + context, + node, + /*needsValue*/ true, + createAssignment, + destructuringVisitor); } return visitEachChild(node, destructuringVisitor, context); } @@ -1612,6 +1622,15 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getOrCreateExternalHelpersModuleName(currentSourceFile, compilerOptions); + if (externalHelpersModuleName) { + return createPropertyAccess(externalHelpersModuleName, node); + } + + return node; + } + // When we see an identifier in an expression position that // points to an imported symbol, we should substitute a qualified // reference to the imported symbol if one is needed. diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index bf0a7a4b27e..1deb969d298 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -21,6 +21,7 @@ namespace ts { export function transformTypeScript(context: TransformationContext) { const { startLexicalEnvironment, + resumeLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, } = context; @@ -48,7 +49,6 @@ namespace ts { let currentNamespaceContainerName: Identifier; let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentScopeFirstDeclarationsOfName: Map; - let helperState: EmitHelperState; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -81,21 +81,11 @@ namespace ts { } currentSourceFile = node; - currentScope = node; - currentScopeFirstDeclarationsOfName = createMap(); - helperState = { currentSourceFile, compilerOptions }; - let visited = visitEachChild(node, sourceElementVisitor, context); - if (compilerOptions.alwaysStrict) { - visited = updateSourceFileNode(visited, ensureUseStrict(visited.statements)); - } - - addEmitHelpers(visited, helperState.requestedHelpers); + const visited = saveStateAndInvoke(node, visitSourceFile); + addEmitHelpers(visited, context.readEmitHelpers(/*onlyScoped*/ false)); currentSourceFile = undefined; - currentScope = undefined; - currentScopeFirstDeclarationsOfName = undefined; - helperState = undefined; return visited; } @@ -123,6 +113,32 @@ namespace ts { return visited; } + /** + * Performs actions that should always occur immediately before visiting a node. + * + * @param node The node to visit. + */ + function onBeforeVisitNode(node: Node) { + switch (node.kind) { + case SyntaxKind.SourceFile: + case SyntaxKind.CaseBlock: + case SyntaxKind.ModuleBlock: + case SyntaxKind.Block: + currentScope = node; + currentScopeFirstDeclarationsOfName = undefined; + break; + + case SyntaxKind.ClassDeclaration: + case SyntaxKind.FunctionDeclaration: + if (hasModifier(node, ModifierFlags.Ambient)) { + break; + } + + recordEmittedDeclarationInScope(node); + break; + } + } + /** * General-purpose node visitor. * @@ -451,29 +467,10 @@ namespace ts { } } - /** - * Performs actions that should always occur immediately before visiting a node. - * - * @param node The node to visit. - */ - function onBeforeVisitNode(node: Node) { - switch (node.kind) { - case SyntaxKind.CaseBlock: - case SyntaxKind.ModuleBlock: - case SyntaxKind.Block: - currentScope = node; - currentScopeFirstDeclarationsOfName = undefined; - break; - - case SyntaxKind.ClassDeclaration: - case SyntaxKind.FunctionDeclaration: - if (hasModifier(node, ModifierFlags.Ambient)) { - break; - } - - recordEmittedDeclarationInScope(node); - break; - } + function visitSourceFile(node: SourceFile) { + return updateSourceFileNode( + node, + visitLexicalEnvironment(node.statements, sourceElementVisitor, context, /*start*/ 0, compilerOptions.alwaysStrict)); } /** @@ -845,9 +842,8 @@ namespace ts { // downlevel the '...args' portion less efficiently by naively copying the contents of 'arguments' to an array. // Instead, we'll avoid using a rest parameter and spread into the super call as // 'super(...arguments)' instead of 'super(...args)', as you can see in "transformConstructorBody". - return constructor - ? visitNodes(constructor.parameters, visitor, isParameter) - : []; + return visitParameterList(constructor && constructor.parameters, visitor, context) + || []; } /** @@ -859,11 +855,11 @@ namespace ts { * @param hasExtendsClause A value indicating whether the class has an extends clause. */ function transformConstructorBody(node: ClassExpression | ClassDeclaration, constructor: ConstructorDeclaration, hasExtendsClause: boolean) { - const statements: Statement[] = []; + let statements: Statement[] = []; let indexOfFirstStatement = 0; // The body of a constructor is a new lexical environment - startLexicalEnvironment(); + resumeLexicalEnvironment(); if (constructor) { indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements); @@ -918,16 +914,14 @@ namespace ts { } // End the lexical environment. - addRange(statements, endLexicalEnvironment()); - return setMultiLine( - createBlock( - createNodeArray( - statements, - /*location*/ constructor ? constructor.body.statements : node.members - ), - /*location*/ constructor ? constructor.body : /*location*/ undefined + statements = mergeLexicalEnvironment(statements, endLexicalEnvironment()); + return createBlock( + createNodeArray( + statements, + /*location*/ constructor ? constructor.body.statements : node.members ), - true + /*location*/ constructor ? constructor.body : /*location*/ undefined, + /*multiLine*/ true ); } @@ -941,7 +935,7 @@ namespace ts { if (ctor.body) { const statements = ctor.body.statements; // add prologue directives to the list (if any) - const index = addPrologueDirectives(result, statements, /*ensureUseStrict*/ false, visitor); + const index = addPrologueDirectives(result, statements, /*ensureUseStrict*/ false, /*ignoreCustomPrologue*/ false, visitor); if (index === statements.length) { // list contains nothing but prologue directives (or empty) - exit return index; @@ -1385,7 +1379,7 @@ namespace ts { : undefined; const helper = createDecorateHelper( - helperState, + context, decoratorExpressions, prefix, memberName, @@ -1423,7 +1417,7 @@ namespace ts { const classAlias = classAliases && classAliases[getOriginalNodeId(node)]; const localName = getLocalName(node, /*allowComments*/ false, /*allowSourceMaps*/ true); - const decorate = createDecorateHelper(helperState, decoratorExpressions, localName); + const decorate = createDecorateHelper(context, decoratorExpressions, localName); const expression = createAssignment(localName, classAlias ? createAssignment(classAlias, decorate) : decorate); setEmitFlags(expression, EmitFlags.NoComments); setSourceMapRange(expression, moveRangePastDecorators(node)); @@ -1451,7 +1445,7 @@ namespace ts { expressions = []; for (const decorator of decorators) { const helper = createParamHelper( - helperState, + context, transformDecorator(decorator), parameterOffset, /*location*/ decorator.expression); @@ -1481,13 +1475,13 @@ namespace ts { function addOldTypeMetadata(node: Declaration, decoratorExpressions: Expression[]) { if (compilerOptions.emitDecoratorMetadata) { if (shouldAddTypeMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(helperState, "design:type", serializeTypeOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:type", serializeTypeOfNode(node))); } if (shouldAddParamTypesMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(helperState, "design:paramtypes", serializeParameterTypesOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:paramtypes", serializeParameterTypesOfNode(node))); } if (shouldAddReturnTypeMetadata(node)) { - decoratorExpressions.push(createMetadataHelper(helperState, "design:returntype", serializeReturnTypeOfNode(node))); + decoratorExpressions.push(createMetadataHelper(context, "design:returntype", serializeReturnTypeOfNode(node))); } } } @@ -1505,7 +1499,7 @@ namespace ts { (properties || (properties = [])).push(createPropertyAssignment("returnType", createArrowFunction(/*modifiers*/ undefined, /*typeParameters*/ undefined, [], /*type*/ undefined, createToken(SyntaxKind.EqualsGreaterThanToken), serializeReturnTypeOfNode(node)))); } if (properties) { - decoratorExpressions.push(createMetadataHelper(helperState, "design:typeinfo", createObjectLiteral(properties, /*location*/ undefined, /*multiLine*/ true))); + decoratorExpressions.push(createMetadataHelper(context, "design:typeinfo", createObjectLiteral(properties, /*location*/ undefined, /*multiLine*/ true))); } } } @@ -2003,7 +1997,13 @@ namespace ts { return undefined; } - return visitEachChild(node, visitor, context); + return updateConstructor( + node, + visitNodes(node.decorators, visitor, isDecorator), + visitNodes(node.modifiers, visitor, isModifier), + visitParameterList(node.parameters, visitor, context), + visitFunctionBody(node.body, visitor, context) + ); } /** @@ -2020,26 +2020,23 @@ namespace ts { if (!shouldEmitFunctionLikeDeclaration(node)) { return undefined; } - - const method = createMethod( + const updated = updateMethod( + node, /*decorators*/ undefined, visitNodes(node.modifiers, modifierVisitor, isModifier), - node.asteriskToken, visitPropertyNameOfClassElement(node), /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + visitFunctionBody(node.body, visitor, context) ); - - // While we emit the source map for the node after skipping decorators and modifiers, - // we need to emit the comments for the original range. - setCommentRange(method, node); - setSourceMapRange(method, moveRangePastDecorators(node)); - setOriginalNode(method, node); - - return method; + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setSourceMapRange(updated, moveRangePastDecorators(node)); + } + return updated; } /** @@ -2065,24 +2062,22 @@ namespace ts { if (!shouldEmitAccessorDeclaration(node)) { return undefined; } - - const accessor = createGetAccessor( + const updated = updateGetAccessor( + node, /*decorators*/ undefined, visitNodes(node.modifiers, modifierVisitor, isModifier), visitPropertyNameOfClassElement(node), - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - node.body ? visitEachChild(node.body, visitor, context) : createBlock([]), - /*location*/ node + visitFunctionBody(node.body, visitor, context) || createBlock([]) ); - - // While we emit the source map for the node after skipping decorators and modifiers, - // we need to emit the comments for the original range. - setOriginalNode(accessor, node); - setCommentRange(accessor, node); - setSourceMapRange(accessor, moveRangePastDecorators(node)); - - return accessor; + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setSourceMapRange(updated, moveRangePastDecorators(node)); + } + return updated; } /** @@ -2098,23 +2093,21 @@ namespace ts { if (!shouldEmitAccessorDeclaration(node)) { return undefined; } - - const accessor = createSetAccessor( + const updated = updateSetAccessor( + node, /*decorators*/ undefined, visitNodes(node.modifiers, modifierVisitor, isModifier), visitPropertyNameOfClassElement(node), - visitNodes(node.parameters, visitor, isParameter), - node.body ? visitEachChild(node.body, visitor, context) : createBlock([]), - /*location*/ node + visitParameterList(node.parameters, visitor, context), + visitFunctionBody(node.body, visitor, context) || createBlock([]) ); - - // While we emit the source map for the node after skipping decorators and modifiers, - // we need to emit the comments for the original range. - setOriginalNode(accessor, node); - setCommentRange(accessor, node); - setSourceMapRange(accessor, moveRangePastDecorators(node)); - - return accessor; + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setSourceMapRange(updated, moveRangePastDecorators(node)); + } + return updated; } /** @@ -2131,27 +2124,22 @@ namespace ts { if (!shouldEmitFunctionLikeDeclaration(node)) { return createNotEmittedStatement(node); } - - const func = createFunctionDeclaration( + const updated = updateFunctionDeclaration( + node, /*decorators*/ undefined, visitNodes(node.modifiers, modifierVisitor, isModifier), - node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + visitFunctionBody(node.body, visitor, context) || createBlock([]) ); - setOriginalNode(func, node); - if (isNamespaceExport(node)) { - const statements: Statement[] = [func]; + const statements: Statement[] = [updated]; addExportMemberAssignment(statements, node); return statements; } - - return func; + return updated; } /** @@ -2163,24 +2151,19 @@ namespace ts { * @param node The function expression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - if (nodeIsMissing(node.body)) { + if (!shouldEmitFunctionLikeDeclaration(node)) { return createOmittedExpression(); } - - const func = createFunctionExpression( + const updated = updateFunctionExpression( + node, visitNodes(node.modifiers, modifierVisitor, isModifier), - node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + visitFunctionBody(node.body, visitor, context) || createBlock([]) ); - - setOriginalNode(func, node); - - return func; + return updated; } /** @@ -2189,62 +2172,15 @@ namespace ts { * - The node has type annotations */ function visitArrowFunction(node: ArrowFunction) { - const func = createArrowFunction( + const updated = updateArrowFunction( + node, visitNodes(node.modifiers, modifierVisitor, isModifier), /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - node.equalsGreaterThanToken, - transformConciseBody(node), - /*location*/ node + visitFunctionBody(node.body, visitor, context) ); - - setOriginalNode(func, node); - - return func; - } - - function transformFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody { - return transformFunctionBodyWorker(node.body); - } - - function transformFunctionBodyWorker(body: Block, start = 0) { - const savedCurrentScope = currentScope; - const savedCurrentScopeFirstDeclarationsOfName = currentScopeFirstDeclarationsOfName; - currentScope = body; - currentScopeFirstDeclarationsOfName = createMap(); - startLexicalEnvironment(); - - const statements = visitNodes(body.statements, visitor, isStatement, start); - const visited = updateBlock(body, statements); - const declarations = endLexicalEnvironment(); - currentScope = savedCurrentScope; - currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName; - return mergeFunctionBodyLexicalEnvironment(visited, declarations); - } - - function transformConciseBody(node: ArrowFunction): ConciseBody { - 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 = mergeFunctionBodyLexicalEnvironment(visited, declarations); - if (forceBlockFunctionBody && !isBlock(merged)) { - return createBlock([ - createReturn(merged) - ]); - } - else { - return merged; - } - } + return updated; } /** @@ -2312,7 +2248,12 @@ namespace ts { function transformInitializedVariable(node: VariableDeclaration): Expression { const name = node.name; if (isBindingPattern(name)) { - return flattenDestructuringToExpression(node, /*needsValue*/ false, createNamespaceExportExpression, hoistVariableDeclaration, visitor); + return flattenDestructuringToExpression( + context, + node, + /*needsValue*/ false, + createNamespaceExportExpression, + visitor); } else { return createAssignment( @@ -2498,7 +2439,7 @@ namespace ts { const savedCurrentNamespaceLocalName = currentNamespaceContainerName; currentNamespaceContainerName = localName; - const statements: Statement[] = []; + let statements: Statement[] = []; startLexicalEnvironment(); addRange(statements, map(node.members, transformEnumMember)); addRange(statements, endLexicalEnvironment()); @@ -2781,7 +2722,7 @@ namespace ts { currentNamespace = node; currentScopeFirstDeclarationsOfName = undefined; - const statements: Statement[] = []; + let statements: Statement[] = []; startLexicalEnvironment(); let statementsLocation: TextRange; @@ -3217,6 +3158,11 @@ namespace ts { */ function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void { const savedApplicableSubstitutions = applicableSubstitutions; + const savedCurrentSourceFile = currentSourceFile; + + if (isSourceFile(node)) { + currentSourceFile = node; + } if (enabledSubstitutions & TypeScriptSubstitutionFlags.NamespaceExports && isTransformedModuleDeclaration(node)) { applicableSubstitutions |= TypeScriptSubstitutionFlags.NamespaceExports; @@ -3229,6 +3175,7 @@ namespace ts { previousOnEmitNode(emitContext, node, emitCallback); applicableSubstitutions = savedApplicableSubstitutions; + currentSourceFile = savedCurrentSourceFile; } /** @@ -3300,6 +3247,7 @@ namespace ts { const clone = getSynthesizedClone(classAlias); setSourceMapRange(clone, node); setCommentRange(clone, node); + debugger; return clone; } } @@ -3311,7 +3259,7 @@ namespace ts { function trySubstituteNamespaceExportedName(node: Identifier): Expression { // If this is explicitly a local name, do not substitute. - if (enabledSubstitutions & applicableSubstitutions && !isLocalName(node)) { + if (enabledSubstitutions & applicableSubstitutions && !isGeneratedIdentifier(node) && !isLocalName(node)) { // If we are nested within a namespace declaration, we may need to qualifiy // an identifier that is exported from a merged namespace. const container = resolver.getReferencedExportContainer(node, /*prefixLocals*/ false); @@ -3367,32 +3315,7 @@ namespace ts { } } - function createParamHelper(helperState: EmitHelperState, expression: Expression, parameterOffset: number, location?: TextRange) { - requestEmitHelper(helperState, paramHelper); - return createCall( - getHelperName(helperState, "__param"), - /*typeArguments*/ undefined, - [ - createLiteral(parameterOffset), - expression - ], - location - ); - } - - function createMetadataHelper(helperState: EmitHelperState, metadataKey: string, metadataValue: Expression) { - requestEmitHelper(helperState, metadataHelper); - return createCall( - getHelperName(helperState, "__metadata"), - /*typeArguments*/ undefined, - [ - createLiteral(metadataKey), - metadataValue - ] - ); - } - - function createDecorateHelper(helperState: EmitHelperState, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { + function createDecorateHelper(context: TransformationContext, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { const argumentsArray: Expression[] = []; argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); argumentsArray.push(target); @@ -3403,8 +3326,12 @@ namespace ts { } } - requestEmitHelper(helperState, decorateHelper); - return createCall(getHelperName(helperState, "__decorate"), /*typeArguments*/ undefined, argumentsArray, location); + context.requestEmitHelper(decorateHelper); + return createCall( + getHelperName("__decorate"), + /*typeArguments*/ undefined, + argumentsArray, + location); } const decorateHelper: EmitHelper = { @@ -3420,6 +3347,18 @@ namespace ts { };` }; + function createMetadataHelper(context: TransformationContext, metadataKey: string, metadataValue: Expression) { + context.requestEmitHelper(metadataHelper); + return createCall( + getHelperName("__metadata"), + /*typeArguments*/ undefined, + [ + createLiteral(metadataKey), + metadataValue + ] + ); + } + const metadataHelper: EmitHelper = { name: "typescript:metadata", scoped: false, @@ -3430,6 +3369,19 @@ namespace ts { };` }; + function createParamHelper(context: TransformationContext, expression: Expression, parameterOffset: number, location?: TextRange) { + context.requestEmitHelper(paramHelper); + return createCall( + getHelperName("__param"), + /*typeArguments*/ undefined, + [ + createLiteral(parameterOffset), + expression + ], + location + ); + } + const paramHelper: EmitHelper = { name: "typescript:param", scoped: false, diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 562e6cef26a..d880025662f 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1497,6 +1497,10 @@ namespace ts { expression: Expression; } + export interface PrologueDirective extends ExpressionStatement { + expression: StringLiteral; + } + export interface IfStatement extends Statement { kind: SyntaxKind.IfStatement; expression: Expression; @@ -3474,6 +3478,7 @@ namespace ts { /* @internal */ export const enum EmitFlags { + HelperName = 1 << 0, UMDDefine = 1 << 4, // This node should be replaced with the UMD define helper. SingleLine = 1 << 5, // The contents of this node should be emitted on a single line. AdviseOnEmitNode = 1 << 6, // The printer should invoke the onEmitNode callback when printing this node. @@ -3517,16 +3522,128 @@ namespace ts { Unspecified, // Emitting an otherwise unspecified node } + /* @internal */ + export interface EmitHost extends ScriptReferenceHost { + getSourceFiles(): SourceFile[]; + + /* @internal */ + isSourceFileFromExternalLibrary(file: SourceFile): boolean; + + getCommonSourceDirectory(): string; + getCanonicalFileName(fileName: string): string; + getNewLine(): string; + + isEmitBlocked(emitFileName: string): boolean; + + writeFile: WriteFileCallback; + } + /** Additional context provided to `visitEachChild` */ /* @internal */ - export interface LexicalEnvironment { - /** Starts a new lexical environment. */ + export interface TransformationContext { + getCompilerOptions(): CompilerOptions; + getEmitResolver(): EmitResolver; + getEmitHost(): EmitHost; + + /** + * Hoists a function declaration to the current lexical environment. + */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + + /** + * Hoists a variable declaration to the current lexical environment. + */ + hoistVariableDeclaration(name: Identifier): void; + + /** + * Starts tracking hoisted declarations in a new lexical environment. + */ startLexicalEnvironment(): void; - /** Ends a lexical environment, returning any declarations. */ + suspendLexicalEnvironment(): void; + resumeLexicalEnvironment(): void; + + /** + * Ends a lexical environment, returning any declarations. + */ endLexicalEnvironment(): Statement[]; + + /** + * Requests an emit helper. + */ + requestEmitHelper(helper: EmitHelper): void; + + /** + * Gets and resets the requested emit helpers. + * + * @param onlyScoped Only read emit helpers whose `scoped` property is `true`. + */ + readEmitHelpers(onlyScoped: boolean): EmitHelper[]; + + /** + * Enables expression substitutions in the pretty printer for the provided SyntaxKind. + */ + enableSubstitution(kind: SyntaxKind): void; + + /** + * Determines whether expression substitutions are enabled for the provided node. + */ + isSubstitutionEnabled(node: Node): boolean; + + /** + * Hook used by transformers to substitute expressions just before they + * are emitted by the pretty printer. + */ + onSubstituteNode?: (emitContext: EmitContext, node: Node) => Node; + + /** + * Enables before/after emit notifications in the pretty printer for the provided + * SyntaxKind. + */ + enableEmitNotification(kind: SyntaxKind): void; + + /** + * Determines whether before/after emit notifications should be raised in the pretty + * printer when it emits a node. + */ + isEmitNotificationEnabled(node: Node): boolean; + + /** + * Hook used to allow transformers to capture state before or after + * the printer emits a node. + */ + onEmitNode?: (emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) => void; } + /* @internal */ + export interface TransformationResult { + /** + * Gets the transformed source files. + */ + transformed: SourceFile[]; + + /** + * Emits the substitute for a node, if one is available; otherwise, emits the node. + * + * @param emitContext The current emit context. + * @param node The node to substitute. + * @param emitCallback A callback used to emit the node or its substitute. + */ + emitNodeWithSubstitution(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; + + /** + * Emits a node with possible notification. + * + * @param emitContext The current emit context. + * @param node The node to emit. + * @param emitCallback A callback used to emit the node. + */ + emitNodeWithNotification(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void): void; + } + + /* @internal */ + export type Transformer = (context: TransformationContext) => (node: SourceFile) => SourceFile; + export interface TextSpan { start: number; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 46b91c1d656..f531f6d03b7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -28,21 +28,6 @@ namespace ts { string(): string; } - export interface EmitHost extends ScriptReferenceHost { - getSourceFiles(): SourceFile[]; - - /* @internal */ - isSourceFileFromExternalLibrary(file: SourceFile): boolean; - - getCommonSourceDirectory(): string; - getCanonicalFileName(fileName: string): string; - getNewLine(): string; - - isEmitBlocked(emitFileName: string): boolean; - - writeFile: WriteFileCallback; - } - // Pool writers to avoid needing to allocate them for every symbol we write. const stringWriters: StringSymbolWriter[] = []; export function getSingleLineStringWriter(): StringSymbolWriter { @@ -613,7 +598,7 @@ namespace ts { return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; } - export function isPrologueDirective(node: Node): boolean { + export function isPrologueDirective(node: Node): node is PrologueDirective { return node.kind === SyntaxKind.ExpressionStatement && (node).expression.kind === SyntaxKind.StringLiteral; } @@ -886,6 +871,19 @@ namespace ts { return false; } + export function getEnclosedStatement(node: LabeledStatement): { statement: Statement; enclosingLabeledStatements: LabeledStatement[]; } { + switch (node.statement.kind) { + case SyntaxKind.LabeledStatement: + const result = getEnclosedStatement(node.statement); + if (result) { + result.enclosingLabeledStatements.push(node); + } + return result; + default: + return { statement: node.statement, enclosingLabeledStatements: [node] }; + } + } + export function isFunctionBlock(node: Node) { return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent); diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index e30132b05f5..6c7156603f5 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -4,7 +4,7 @@ /* @internal */ namespace ts { - export type VisitResult = T | T[]; + export type VisitResult = T[] | T | undefined; /** * Describes an edge of a Node, used when traversing a syntax tree. @@ -653,6 +653,54 @@ namespace ts { return updated || nodes; } + /** + * Starts a new lexical environment and visits a statement list, ending the lexical environment + * and merging hoisted declarations upon completion. + */ + export function visitLexicalEnvironment(statements: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext, start?: number, ensureUseStrict?: boolean) { + context.startLexicalEnvironment(); + statements = visitNodes(statements, visitor, isStatement, start); + if (ensureUseStrict && !startsWithUseStrict(statements)) { + statements = createNodeArray([createStatement(createLiteral("use strict")), ...statements], statements); + } + statements = mergeLexicalEnvironment(statements, context.endLexicalEnvironment()); + return statements; + } + + /** + * Starts a new lexical environment and visits a parameter list, suspending the lexical + * environment upon completion. + */ + export function visitParameterList(nodes: NodeArray, visitor: (node: Node) => VisitResult, context: TransformationContext) { + context.startLexicalEnvironment(); + const updated = visitNodes(nodes, visitor, isParameterDeclaration); + context.suspendLexicalEnvironment(); + return updated; + } + + /** + * Resumes a suspended lexical environment and visits a function body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: FunctionBody, visitor: (node: Node) => VisitResult, context: TransformationContext, optional?: boolean): FunctionBody; + + /** + * Resumes a suspended lexical environment and visits a concise body, ending the lexical + * environment and merging hoisted declarations upon completion. + */ + export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext): ConciseBody; + export function visitFunctionBody(node: ConciseBody, visitor: (node: Node) => VisitResult, context: TransformationContext, optional?: boolean) { + context.resumeLexicalEnvironment(); + const updated = visitNode(node, visitor, isConciseBody, optional); + const declarations = context.endLexicalEnvironment(); + if (some(declarations)) { + const block = toFunctionBody(updated); + const statements = mergeLexicalEnvironment(block.statements, declarations); + return updateBlock(block, statements); + } + return updated; + } + /** * Visits each child of a Node using the supplied visitor, possibly returning a new Node of the same kind in its place. * @@ -660,8 +708,8 @@ namespace ts { * @param visitor The callback used to visit each child. * @param context A lexical environment context for the visitor. */ - export function visitEachChild(node: T, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): T; - export function visitEachChild(node: Node, visitor: (node: Node) => VisitResult, context: LexicalEnvironment): Node { + export function visitEachChild(node: T, visitor: (node: Node) => VisitResult, context: TransformationContext): T; + export function visitEachChild(node: Node, visitor: (node: Node) => VisitResult, context: TransformationContext): Node { if (node === undefined) { return undefined; } @@ -714,41 +762,33 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), visitNodes((node).typeParameters, visitor, isTypeParameter), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), + visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.Constructor: return updateConstructor(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitParameterList((node).parameters, visitor, context), + visitFunctionBody((node).body, visitor, context, /*optional*/ true)); case SyntaxKind.GetAccessor: return updateGetAccessor(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), + visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitFunctionBody((node).body, visitor, context, /*optional*/ true)); case SyntaxKind.SetAccessor: return updateSetAccessor(node, visitNodes((node).decorators, visitor, isDecorator), visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitParameterList((node).parameters, visitor, context), + visitFunctionBody((node).body, visitor, context, /*optional*/ true)); // Binding patterns case SyntaxKind.ObjectBindingPattern: @@ -810,21 +850,17 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), visitNodes((node).typeParameters, visitor, isTypeParameter), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), + visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.ArrowFunction: return updateArrowFunction(node, visitNodes((node).modifiers, visitor, isModifier), visitNodes((node).typeParameters, visitor, isTypeParameter), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), + visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isConciseBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitFunctionBody((node).body, visitor, context)); case SyntaxKind.DeleteExpression: return updateDelete(node, @@ -995,11 +1031,9 @@ namespace ts { visitNodes((node).modifiers, visitor, isModifier), visitNode((node).name, visitor, isPropertyName), visitNodes((node).typeParameters, visitor, isTypeParameter), - (context.startLexicalEnvironment(), visitNodes((node).parameters, visitor, isParameter)), + visitParameterList((node).parameters, visitor, context), visitNode((node).type, visitor, isTypeNode, /*optional*/ true), - mergeFunctionBodyLexicalEnvironment( - visitNode((node).body, visitor, isFunctionBody, /*optional*/ true), - context.endLexicalEnvironment())); + visitFunctionBody((node).body, visitor, context, /*optional*/ true)); case SyntaxKind.ClassDeclaration: return updateClassDeclaration(node, @@ -1127,13 +1161,8 @@ namespace ts { // Top-level nodes case SyntaxKind.SourceFile: - context.startLexicalEnvironment(); return updateSourceFileNode(node, - createNodeArray( - concatenate( - visitNodes((node).statements, visitor, isStatement), - context.endLexicalEnvironment()), - (node).statements)); + visitLexicalEnvironment((node).statements, visitor, context)); // Transformation nodes case SyntaxKind.PartiallyEmittedExpression: @@ -1163,39 +1192,31 @@ namespace ts { } return updated ? updateNode(updated, node) : node; } - - // return node; } /** - * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. + * Merges generated lexical declarations into a statement list, creating a new statement list. * - * @param node The ConciseBody of an arrow function. + * @param statements The statements. * @param declarations The lexical declarations to merge. */ - export function mergeFunctionBodyLexicalEnvironment(body: FunctionBody, declarations: Statement[]): FunctionBody; + export function mergeLexicalEnvironment(statements: NodeArray, declarations: Statement[]): NodeArray; /** - * Merges generated lexical declarations into the ConciseBody of an ArrowFunction. + * Appends generated lexical declarations to an array of statements. * - * @param node The ConciseBody of an arrow function. + * @param statements The statements. * @param declarations The lexical declarations to merge. */ - export function mergeFunctionBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[]): ConciseBody; - - export function mergeFunctionBodyLexicalEnvironment(body: ConciseBody, declarations: Statement[]): ConciseBody { - if (body && declarations !== undefined && declarations.length > 0) { - if (isBlock(body)) { - return updateBlock(body, createNodeArray(concatenate(body.statements, declarations), body.statements)); - } - else { - return createBlock( - createNodeArray([createReturn(body, /*location*/ body), ...declarations], body), - /*location*/ body, - /*multiLine*/ true); - } + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]): Statement[]; + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]) { + if (!some(declarations)) { + return statements; } - return body; + + return isNodeArray(statements) + ? createNodeArray(concatenate(statements, declarations), statements) + : addRange(statements, declarations); } /**