From 12cd0bfb69b88fffc5135a4bccd3eb6b6c4c35b1 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 8 Nov 2016 16:32:41 -0800 Subject: [PATCH] Remove EmitHelperState, general helper cleanup. --- src/compiler/factory.ts | 57 ++-- src/compiler/transformer.ts | 178 +++++------ src/compiler/transformers/es2015.ts | 246 ++++++++------- src/compiler/transformers/es2017.ts | 141 ++++----- src/compiler/transformers/generators.ts | 13 +- src/compiler/transformers/jsx.ts | 13 +- src/compiler/transformers/module/es2015.ts | 60 +++- src/compiler/transformers/module/module.ts | 10 +- src/compiler/transformers/module/system.ts | 10 +- src/compiler/transformers/ts.ts | 345 +++++++++------------ src/compiler/types.ts | 135 +++++++- src/compiler/utilities.ts | 20 +- src/compiler/visitor.ts | 117 ++++--- 13 files changed, 725 insertions(+), 620 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index cd4f4561fd9..f8b71a733cd 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1695,23 +1695,8 @@ namespace ts { // Helpers - export interface EmitHelperState { - currentSourceFile: SourceFile; - compilerOptions: CompilerOptions; - requestedHelpers?: EmitHelper[]; - } - - export function getHelperName(helperState: EmitHelperState, name: string) { - const externalHelpersModuleName = getOrCreateExternalHelpersModuleName(helperState.currentSourceFile, helperState.compilerOptions); - return externalHelpersModuleName - ? createPropertyAccess(externalHelpersModuleName, name) - : createIdentifier(name); - } - - export function requestEmitHelper(helperState: EmitHelperState, helper: EmitHelper) { - if (!contains(helperState.requestedHelpers, helper)) { - helperState.requestedHelpers = append(helperState.requestedHelpers, helper); - } + export function getHelperName(name: string) { + return setEmitFlags(createIdentifier(name), EmitFlags.HelperName | EmitFlags.AdviseOnEmitNode); } export interface CallBinding { @@ -2061,6 +2046,10 @@ namespace ts { // Utilities + export function convertToFunctionBody(node: ConciseBody) { + return isBlock(node) ? node : createBlock([createReturn(node, /*location*/ node)], /*location*/ node); + } + function isUseStrictPrologue(node: ExpressionStatement): boolean { return (node.expression as StringLiteral).text === "use strict"; } @@ -2110,6 +2099,13 @@ namespace ts { return statementOffset; } + export function startsWithUseStrict(statements: Statement[]) { + const firstStatement = firstOrUndefined(statements); + return firstStatement !== undefined + && isPrologueDirective(firstStatement) + && isUseStrictPrologue(firstStatement); + } + /** * Ensures "use strict" directive is added * @@ -2606,7 +2602,7 @@ namespace ts { * * @param node The node. */ - function getOrCreateEmitNode(node: Node) { + export function getOrCreateEmitNode(node: Node) { if (!node.emitNode) { if (isParseTreeNode(node)) { // To avoid holding onto transformation artifacts, we keep track of any @@ -2735,14 +2731,25 @@ namespace ts { return emitNode && emitNode.externalHelpersModuleName; } - export function getOrCreateExternalHelpersModuleName(node: SourceFile, compilerOptions: CompilerOptions) { + export function getOrCreateExternalHelpersModuleNameIfNeeded(node: SourceFile, compilerOptions: CompilerOptions) { if (compilerOptions.importHelpers && (isExternalModule(node) || compilerOptions.isolatedModules)) { - const parseNode = getOriginalNode(node, isSourceFile); - const emitNode = getOrCreateEmitNode(parseNode); - return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText)); + const externalHelpersModuleName = getExternalHelpersModuleName(node); + if (externalHelpersModuleName) { + return externalHelpersModuleName; + } + + const helpers = getEmitHelpers(node); + if (helpers) { + for (const helper of helpers) { + if (!helper.scoped) { + const parseNode = getOriginalNode(node, isSourceFile); + const emitNode = getOrCreateEmitNode(parseNode); + return emitNode.externalHelpersModuleName || (emitNode.externalHelpersModuleName = createUniqueName(externalHelpersModuleNameText)); + } + } + } } } - /** * Adds an EmitHelper to a node. */ @@ -2930,7 +2937,7 @@ namespace ts { hasExportStarsToExportValues: boolean; // whether this module contains export* } - export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver): ExternalModuleInfo { + export function collectExternalModuleInfo(sourceFile: SourceFile, resolver: EmitResolver, compilerOptions: CompilerOptions): ExternalModuleInfo { const externalImports: (ImportDeclaration | ImportEqualsDeclaration | ExportDeclaration)[] = []; const exportSpecifiers = createMap(); const exportedBindings = createMap(); @@ -2940,7 +2947,7 @@ namespace ts { let exportEquals: ExportAssignment = undefined; let hasExportStarsToExportValues = false; - const externalHelpersModuleName = getExternalHelpersModuleName(sourceFile); + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(sourceFile, compilerOptions); const externalHelpersImportDeclaration = externalHelpersModuleName && createImportDeclaration( /*decorators*/ undefined, /*modifiers*/ undefined, diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index ee9c081e6eb..1ae123ce5ca 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,18 @@ 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 = false; + + 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 emitHelpers: EmitHelper[]; // The transformation context is provided to each transformer as part of transformer // initialization. @@ -164,10 +90,14 @@ namespace ts { getCompilerOptions: () => host.getCompilerOptions(), getEmitResolver: () => resolver, getEmitHost: () => host, + startLexicalEnvironment, + suspendLexicalEnvironment, + resumeLexicalEnvironment, + endLexicalEnvironment, hoistVariableDeclaration, hoistFunctionDeclaration, - startLexicalEnvironment, - endLexicalEnvironment, + requestEmitHelper, + readEmitHelpers, onSubstituteNode: (_emitContext, node) => node, enableSubstitution, isSubstitutionEnabled, @@ -183,7 +113,7 @@ namespace ts { const transformed = map(sourceFiles, transformSourceFile); // Disable modification of the lexical environment. - lexicalEnvironmentDisabled = true; + scopeModificationDisabled = true; return { transformed, @@ -278,13 +208,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,31 +222,46 @@ 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); } } + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + function suspendLexicalEnvironment(): void { + Debug.assert(!scopeModificationDisabled, "Cannot suspend a lexical environment during the print phase."); + Debug.assert(!lexicalEnvironmentSuspended, "Lexical environment is already suspended."); + lexicalEnvironmentSuspended = true; + } + + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + function resumeLexicalEnvironment(): void { + Debug.assert(!scopeModificationDisabled, "Cannot resume a lexical environment during the print phase."); + Debug.assert(lexicalEnvironmentSuspended, "Lexical environment is not suspended suspended."); + lexicalEnvironmentSuspended = false; + } + /** * Starts a new lexical environment. Any existing hoisted variable or function declarations * 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; } /** @@ -324,18 +269,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 +295,27 @@ namespace ts { // Restore the previous lexical environment. lexicalEnvironmentStackOffset--; - hoistedVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; - hoistedFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentVariableDeclarations = lexicalEnvironmentVariableDeclarationsStack[lexicalEnvironmentStackOffset]; + lexicalEnvironmentFunctionDeclarations = lexicalEnvironmentFunctionDeclarationsStack[lexicalEnvironmentStackOffset]; + if (lexicalEnvironmentStackOffset === 0) { + lexicalEnvironmentVariableDeclarationsStack = []; + lexicalEnvironmentFunctionDeclarationsStack = []; + } + return statements; } + + function requestEmitHelper(helper: EmitHelper): void { + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); + Debug.assert(!helper.scoped, "Cannot request a scoped emit helper."); + emitHelpers = append(emitHelpers, helper); + } + + function readEmitHelpers(): EmitHelper[] | undefined { + Debug.assert(!scopeModificationDisabled, "Cannot modify the lexical environment during the print phase."); + const helpers = emitHelpers; + emitHelpers = undefined; + return helpers; + } } } diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 60d1ad95682..733e47eaa67 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -165,11 +165,11 @@ namespace ts { export function transformES2015(context: TransformationContext) { const { startLexicalEnvironment, + resumeLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, } = context; - const compilerOptions = context.getCompilerOptions(); const resolver = context.getEmitResolver(); const previousOnSubstituteNode = context.onSubstituteNode; const previousOnEmitNode = context.onEmitNode; @@ -187,7 +187,6 @@ namespace ts { let enclosingNonArrowFunction: FunctionLikeDeclaration; let enclosingNonAsyncFunctionBody: FunctionLikeDeclaration | ClassElement; let isInConstructorWithCapturedSuper: boolean; - let helperState: EmitHelperState; /** * Used to track if we are emitting body of the converted loop @@ -210,14 +209,12 @@ namespace ts { currentSourceFile = node; currentText = node.text; - helperState = { currentSourceFile, compilerOptions }; - const visited = visitNode(node, visitor, isSourceFile); - addEmitHelpers(visited, helperState.requestedHelpers); + const visited = saveStateAndInvoke(node, visitSourceFile); + addEmitHelpers(visited, context.readEmitHelpers()); currentSourceFile = undefined; currentText = undefined; - helperState = undefined; return visited; } @@ -264,6 +261,47 @@ namespace ts { return visited; } + 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 returnCapturedThis(node: Node): Node { return setOriginalNode(createReturn(createIdentifier("_this")), node); } @@ -288,7 +326,7 @@ namespace ts { else if (node.transformFlags & TransformFlags.ContainsES2015 || (isInConstructorWithCapturedSuper && !isExpression(node))) { // we want to dive in this branch either if node has children with ES2015 specific syntax // or we are inside constructor that captures result of the super call so all returns without expression should be - // rewritten. Note: we skip expressions since returns should never appear there + // rewritten. Note: we skip expressions since returns should never appear there return visitEachChild(node, visitor, context); } else { @@ -426,6 +464,9 @@ namespace ts { case SyntaxKind.YieldExpression: return visitYieldExpression(node); + case SyntaxKind.SpreadElementExpression: + return visitSpreadElementExpression(node); + case SyntaxKind.SuperKeyword: return visitSuperKeyword(); @@ -436,9 +477,6 @@ namespace ts { case SyntaxKind.MethodDeclaration: return visitMethodDeclaration(node); - case SyntaxKind.SourceFile: - return visitSourceFileNode(node); - case SyntaxKind.VariableStatement: return visitVariableStatement(node); @@ -446,48 +484,19 @@ namespace ts { Debug.failBadSyntaxKind(node); return visitEachChild(node, visitor, context); } - } - 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 visitSourceFile(node: SourceFile): SourceFile { + const statements: Statement[] = []; + startLexicalEnvironment(); + const statementOffset = addPrologueDirectives(statements, node.statements, /*ensureUseStrict*/ false, visitor); + addCaptureThisForNodeIfNeeded(statements, node); + addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); + addRange(statements, endLexicalEnvironment()); + return updateSourceFileNode( + node, + createNodeArray(statements, node.statements) + ); } function visitSwitchStatement(node: SwitchStatement): SwitchStatement { @@ -787,7 +796,7 @@ namespace ts { if (extendsClauseElement) { statements.push( createStatement( - createExtendsHelper(helperState, getLocalName(node)), + createExtendsHelper(context, getLocalName(node)), /*location*/ extendsClauseElement ) ); @@ -837,11 +846,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) + || []; } /** @@ -855,14 +861,14 @@ namespace ts { */ function transformConstructorBody(constructor: ConstructorDeclaration | undefined, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments, hasSynthesizedSuper: boolean) { const statements: Statement[] = []; - startLexicalEnvironment(); + 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. @@ -1386,20 +1392,13 @@ namespace ts { 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 ); @@ -1497,8 +1496,17 @@ namespace ts { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { enableSubstitutionsForCapturedThis(); } - - const func = transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined); + const func = createFunctionExpression( + /*modifiers*/ undefined, + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + visitParameterList(node.parameters, visitor, context), + /*type*/ undefined, + transformFunctionBody(node), + node + ); + setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); return func; } @@ -1509,7 +1517,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) + ); } /** @@ -1518,19 +1536,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) + ); } /** @@ -1552,7 +1569,7 @@ namespace ts { node.asteriskToken, name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, saveStateAndInvoke(node, transformFunctionBody), location @@ -1579,7 +1596,8 @@ namespace ts { const body = node.body; let statementOffset: number; - startLexicalEnvironment(); + resumeLexicalEnvironment(); + 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 @@ -1633,15 +1651,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 (!multiLine && declarations && declarations.length) { 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); } @@ -1650,7 +1674,6 @@ namespace ts { setTokenSourceMapRange(block, SyntaxKind.CloseBraceToken, closeBraceLocation); } - setOriginalNode(block, node.body); return block; } @@ -1659,7 +1682,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: @@ -1713,8 +1736,11 @@ 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 flattenDestructuringAssignment(context, node, needsDestructuringValue, hoistVariableDeclaration, visitor); + if (isDestructuringAssignment(node)) { + return flattenDestructuringAssignment(context, node, needsDestructuringValue, hoistVariableDeclaration, visitor); + } + + return visitEachChild(node, visitor, context); } function visitVariableStatement(node: VariableStatement): Statement { @@ -2894,6 +2920,10 @@ namespace ts { ); } + function visitSpreadElementExpression(node: SpreadElementExpression) { + return visitNode(node.expression, visitor, isExpression); + } + /** * Transforms the expression of a SpreadElementExpression node. * @@ -3080,19 +3110,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. * @@ -3254,8 +3271,7 @@ namespace ts { return false; } - const parameter = singleOrUndefined(constructor.parameters); - if (!parameter || !nodeIsSynthesized(parameter) || !parameter.dotDotDotToken) { + if (some(constructor.parameters)) { return false; } @@ -3280,14 +3296,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..eca0e9a1488 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -13,6 +13,7 @@ namespace ts { export function transformES2017(context: TransformationContext) { const { startLexicalEnvironment, + resumeLexicalEnvironment, endLexicalEnvironment, } = context; @@ -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()); currentSourceFile = undefined; - helperState = undefined; return visited; } @@ -142,22 +137,17 @@ namespace ts { */ function visitMethodDeclaration(node: MethodDeclaration) { Debug.assert(hasModifier(node, ModifierFlags.Async)); - const method = createMethod( + const updated = updateMethod( + node, /*decorators*/ undefined, visitNodes(node.modifiers, visitor, isModifier), - node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + transformFunctionBody(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. - setOriginalNode(method, node); - return method; + return updated; } /** @@ -170,20 +160,17 @@ namespace ts { */ function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { Debug.assert(hasModifier(node, ModifierFlags.Async)); - const func = createFunctionDeclaration( + const updated = updateFunctionDeclaration( + node, /*decorators*/ undefined, visitNodes(node.modifiers, visitor, isModifier), - node.asteriskToken, node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + transformFunctionBody(node) ); - - setOriginalNode(func, node); - return func; + return updated; } /** @@ -199,20 +186,18 @@ namespace ts { if (nodeIsMissing(node.body)) { return createOmittedExpression(); } - - const func = createFunctionExpression( - /*modifiers*/ undefined, - node.asteriskToken, + const updated = updateFunctionExpression( + node, + visitNodes(node.modifiers, visitor, isModifier), node.name, /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - transformFunctionBody(node), - /*location*/ node + transformFunctionBody(node) ); - setOriginalNode(func, node); - return func; + setOriginalNode(updated, node); + return updated; } /** @@ -225,42 +210,24 @@ namespace ts { */ function visitArrowFunction(node: ArrowFunction) { Debug.assert(hasModifier(node, ModifierFlags.Async)); - const func = createArrowFunction( + const updated = updateArrowFunction( + node, visitNodes(node.modifiers, visitor, isModifier), /*typeParameters*/ undefined, - visitNodes(node.parameters, visitor, isParameter), + visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - node.equalsGreaterThanToken, - transformConciseBody(node), - /*location*/ node + transformFunctionBody(node) ); - setOriginalNode(func, node); - return func; + setOriginalNode(updated, node); + return updated; } - 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 { - const nodeType = node.original ? (node.original).type : node.type; + function transformFunctionBody(node: MethodDeclaration | AccessorDeclaration | FunctionDeclaration | FunctionExpression): FunctionBody; + function transformFunctionBody(node: ArrowFunction): ConciseBody; + function transformFunctionBody(node: FunctionLikeDeclaration): ConciseBody { + const original = getOriginalNode(node, isFunctionLike); + const nodeType = original.type; const promiseConstructor = languageVersion < ScriptTarget.ES2015 ? getPromiseConstructor(nodeType) : undefined; const isArrowFunction = node.kind === SyntaxKind.ArrowFunction; const hasLexicalArguments = (resolver.getNodeCheckFlags(node) & NodeCheckFlags.CaptureArguments) !== 0; @@ -271,6 +238,7 @@ namespace ts { // passed to `__awaiter` is executed inside of the callback to the // promise constructor. + resumeLexicalEnvironment(); if (!isArrowFunction) { const statements: Statement[] = []; @@ -278,7 +246,7 @@ namespace ts { statements.push( createReturn( createAwaiterHelper( - helperState, + context, hasLexicalArguments, promiseConstructor, transformFunctionBodyWorker(node.body, statementOffset) @@ -286,6 +254,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,37 +274,40 @@ 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 = convertToFunctionBody(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?: number) { 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 = convertToFunctionBody(visitNode(body, visitor, isConciseBody)); + const statements = mergeLexicalEnvironment(visited.statements, endLexicalEnvironment()); + return updateBlock(visited, statements); } } function getPromiseConstructor(type: TypeNode) { - const typeName = getEntityNameFromTypeNode(type); + const typeName = type && getEntityNameFromTypeNode(type); if (typeName && isEntityName(typeName)) { const serializationKind = resolver.getTypeReferenceSerializationKind(typeName); if (serializationKind === TypeReferenceSerializationKind.TypeWithConstructSignatureAndValue @@ -506,7 +479,8 @@ 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 +494,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..6047a8d8edd 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -242,7 +242,6 @@ namespace ts { let currentSourceFile: SourceFile; let renamedCatchVariables: Map; let renamedCatchVariableDeclarations: Map; - let helperState: EmitHelperState; let inGeneratorFunctionBody: boolean; let inStatementContainingYield: boolean; @@ -298,13 +297,11 @@ namespace ts { } currentSourceFile = node; - helperState = { currentSourceFile, compilerOptions }; const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, helperState.requestedHelpers); + addEmitHelpers(visited, context.readEmitHelpers()); currentSourceFile = undefined; - helperState = undefined; return visited; } @@ -2590,7 +2587,7 @@ namespace ts { const buildResult = buildStatements(); return createGeneratorHelper( - helperState, + context, setEmitFlags( createFunctionExpression( /*modifiers*/ undefined, @@ -3083,10 +3080,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]); } diff --git a/src/compiler/transformers/jsx.ts b/src/compiler/transformers/jsx.ts index 8867896e51e..e342f2b7906 100644 --- a/src/compiler/transformers/jsx.ts +++ b/src/compiler/transformers/jsx.ts @@ -6,7 +6,6 @@ namespace ts { export function transformJsx(context: TransformationContext) { const compilerOptions = context.getCompilerOptions(); let currentSourceFile: SourceFile; - let helperState: EmitHelperState; return transformSourceFile; @@ -21,13 +20,11 @@ namespace ts { } currentSourceFile = node; - helperState = { currentSourceFile, compilerOptions }; const visited = visitEachChild(node, visitor, context); - addEmitHelpers(visited, helperState.requestedHelpers); + addEmitHelpers(visited, context.readEmitHelpers()); currentSourceFile = undefined; - helperState = undefined; return visited; } @@ -116,7 +113,7 @@ namespace ts { // a call to the __assign helper. objectProperties = singleOrUndefined(segments); if (!objectProperties) { - objectProperties = createAssignHelper(helperState, segments); + objectProperties = createAssignHelper(context, segments); } } @@ -538,10 +535,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..5611f890164 100644 --- a/src/compiler/transformers/module/es2015.ts +++ b/src/compiler/transformers/module/es2015.ts @@ -5,6 +5,14 @@ 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.enableEmitNotification(SyntaxKind.SourceFile); + context.enableSubstitution(SyntaxKind.Identifier); + + let currentSourceFile: SourceFile; return transformSourceFile; function transformSourceFile(node: SourceFile) { @@ -13,7 +21,7 @@ namespace ts { } if (isExternalModule(node) || compilerOptions.isolatedModules) { - const externalHelpersModuleName = getExternalHelpersModuleName(node); + const externalHelpersModuleName = getOrCreateExternalHelpersModuleNameIfNeeded(node, compilerOptions); if (externalHelpersModuleName) { const statements: Statement[] = []; const statementOffset = addPrologueDirectives(statements, node.statements); @@ -55,5 +63,55 @@ namespace ts { // Elide `export=` as it is not legal with --module ES6 return node.isExportEquals ? undefined : node; } + + // + // Emit Notification + // + + /** + * Hook for node emit. + * + * @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 = getExternalHelpersModuleName(currentSourceFile); + 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 22e4e844a69..7b6fcc60f1b 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -61,7 +61,7 @@ namespace ts { } currentSourceFile = node; - currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver); + currentModuleInfo = moduleInfoMap[getOriginalNodeId(node)] = collectExternalModuleInfo(node, resolver, compilerOptions); // Perform the transformation. const transformModule = transformModuleDelegates[moduleKind] || transformModuleDelegates[ModuleKind.None]; @@ -1187,6 +1187,14 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); + 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 4b24d5c1641..b979f66efd1 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -73,7 +73,7 @@ namespace ts { // see comment to 'substitutePostfixUnaryExpression' for more details // Collect information about the external module and dependency groups. - moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(node, resolver); + moduleInfo = moduleInfoMap[id] = collectExternalModuleInfo(node, resolver, compilerOptions); // Make sure that the name of the 'exports' function does not conflict with // existing identifiers. @@ -1609,6 +1609,14 @@ namespace ts { * @param node The node to substitute. */ function substituteExpressionIdentifier(node: Identifier): Expression { + if (getEmitFlags(node) & EmitFlags.HelperName) { + const externalHelpersModuleName = getExternalHelpersModuleName(currentSourceFile); + 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 02c7b107b25..09bfba2263e 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()); 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) + || []; } /** @@ -863,7 +859,7 @@ namespace ts { let indexOfFirstStatement = 0; // The body of a constructor is a new lexical environment - startLexicalEnvironment(); + resumeLexicalEnvironment(); if (constructor) { indexOfFirstStatement = addPrologueDirectivesAndInitialSuperCall(constructor, statements); @@ -919,15 +915,13 @@ 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 + return createBlock( + createNodeArray( + statements, + /*location*/ constructor ? constructor.body.statements : node.members ), - true + /*location*/ constructor ? constructor.body : /*location*/ undefined, + /*multiLine*/ true ); } @@ -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))); } } } @@ -2020,26 +2014,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 +2056,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 +2087,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 +2118,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; } /** @@ -2166,21 +2148,16 @@ namespace ts { if (nodeIsMissing(node.body)) { 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) ); - - setOriginalNode(func, node); - - return func; + return updated; } /** @@ -2189,62 +2166,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; } /** @@ -3316,7 +3246,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); @@ -3372,10 +3302,20 @@ namespace ts { } } - function createParamHelper(helperState: EmitHelperState, expression: Expression, parameterOffset: number, location?: TextRange) { - requestEmitHelper(helperState, paramHelper); + const paramHelper: EmitHelper = { + name: "typescript:param", + scoped: false, + priority: 4, + text: ` + var __param = (this && this.__param) || function (paramIndex, decorator) { + return function (target, key) { decorator(target, key, paramIndex); } + };` + }; + + function createParamHelper(context: TransformationContext, expression: Expression, parameterOffset: number, location?: TextRange) { + context.requestEmitHelper(paramHelper); return createCall( - getHelperName(helperState, "__param"), + getHelperName("__param"), /*typeArguments*/ undefined, [ createLiteral(parameterOffset), @@ -3385,10 +3325,20 @@ namespace ts { ); } - function createMetadataHelper(helperState: EmitHelperState, metadataKey: string, metadataValue: Expression) { - requestEmitHelper(helperState, metadataHelper); + const metadataHelper: EmitHelper = { + name: "typescript:metadata", + scoped: false, + priority: 3, + text: ` + var __metadata = (this && this.__metadata) || function (k, v) { + if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); + };` + }; + + function createMetadataHelper(context: TransformationContext, metadataKey: string, metadataValue: Expression) { + context.requestEmitHelper(metadataHelper); return createCall( - getHelperName(helperState, "__metadata"), + getHelperName("__metadata"), /*typeArguments*/ undefined, [ createLiteral(metadataKey), @@ -3397,20 +3347,6 @@ namespace ts { ); } - function createDecorateHelper(helperState: EmitHelperState, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { - const argumentsArray: Expression[] = []; - argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); - argumentsArray.push(target); - if (memberName) { - argumentsArray.push(memberName); - if (descriptor) { - argumentsArray.push(descriptor); - } - } - - requestEmitHelper(helperState, decorateHelper); - return createCall(getHelperName(helperState, "__decorate"), /*typeArguments*/ undefined, argumentsArray, location); - } const decorateHelper: EmitHelper = { name: "typescript:decorate", @@ -3425,23 +3361,18 @@ namespace ts { };` }; - const metadataHelper: EmitHelper = { - name: "typescript:metadata", - scoped: false, - priority: 3, - text: ` - var __metadata = (this && this.__metadata) || function (k, v) { - if (typeof Reflect === "object" && typeof Reflect.metadata === "function") return Reflect.metadata(k, v); - };` - }; + function createDecorateHelper(context: TransformationContext, decoratorExpressions: Expression[], target: Expression, memberName?: Expression, descriptor?: Expression, location?: TextRange) { + context.requestEmitHelper(decorateHelper); + const argumentsArray: Expression[] = []; + argumentsArray.push(createArrayLiteral(decoratorExpressions, /*location*/ undefined, /*multiLine*/ true)); + argumentsArray.push(target); + if (memberName) { + argumentsArray.push(memberName); + if (descriptor) { + argumentsArray.push(descriptor); + } + } - const paramHelper: EmitHelper = { - name: "typescript:param", - scoped: false, - priority: 4, - text: ` - var __param = (this && this.__param) || function (paramIndex, decorator) { - return function (target, key) { decorator(target, key, paramIndex); } - };` - }; + return createCall(getHelperName("__decorate"), /*typeArguments*/ undefined, argumentsArray, location); + } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 69ac1d76c9e..178e0bf6de7 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -1503,6 +1503,11 @@ namespace ts { expression: Expression; } + /* @internal */ + export interface PrologueDirective extends ExpressionStatement { + expression: StringLiteral; + } + export interface IfStatement extends Statement { kind: SyntaxKind.IfStatement; expression: Expression; @@ -3541,15 +3546,16 @@ namespace ts { NoTrailingComments = 1 << 10, // Do not emit trailing comments for this node. NoComments = NoLeadingComments | NoTrailingComments, // Do not emit comments for this node. NoNestedComments = 1 << 11, - ExportName = 1 << 12, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). - LocalName = 1 << 13, // Ensure an export prefix is not added for an identifier that points to an exported declaration. - Indented = 1 << 14, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). - NoIndentation = 1 << 15, // Do not indent the node. - AsyncFunctionBody = 1 << 16, - ReuseTempVariableScope = 1 << 17, // Reuse the existing temp variable scope during emit. - CustomPrologue = 1 << 18, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). - NoHoisting = 1 << 19, // Do not hoist this declaration in --module system - HasEndOfDeclarationMarker = 1 << 20, // Declaration has an associated NotEmittedStatement to mark the end of the declaration + HelperName = 1 << 12, + ExportName = 1 << 13, // Ensure an export prefix is added for an identifier that points to an exported declaration with a local name (see SymbolFlags.ExportHasLocal). + LocalName = 1 << 14, // Ensure an export prefix is not added for an identifier that points to an exported declaration. + Indented = 1 << 15, // Adds an explicit extra indentation level for class and function bodies when printing (used to match old emitter). + NoIndentation = 1 << 16, // Do not indent the node. + AsyncFunctionBody = 1 << 17, + ReuseTempVariableScope = 1 << 18, // Reuse the existing temp variable scope during emit. + CustomPrologue = 1 << 19, // Treat the statement as if it were a prologue directive (NOTE: Prologue directives are *not* transformed). + NoHoisting = 1 << 20, // Do not hoist this declaration in --module system + HasEndOfDeclarationMarker = 1 << 21, // Declaration has an associated NotEmittedStatement to mark the end of the declaration } /* @internal */ @@ -3568,16 +3574,123 @@ namespace ts { Unspecified, // Emitting an otherwise unspecified node } - /** Additional context provided to `visitEachChild` */ /* @internal */ - export interface LexicalEnvironment { + 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; + } + + /* @internal */ + export interface TransformationContext { + getCompilerOptions(): CompilerOptions; + getEmitResolver(): EmitResolver; + getEmitHost(): EmitHost; + /** Starts a new lexical environment. */ startLexicalEnvironment(): void; + /** Suspends the current lexical environment, usually after visiting a parameter list. */ + suspendLexicalEnvironment(): void; + + /** Resumes a suspended lexical environment, usually before visiting a function body. */ + resumeLexicalEnvironment(): void; + /** Ends a lexical environment, returning any declarations. */ endLexicalEnvironment(): Statement[]; + + /** + * Hoists a function declaration to the containing scope. + */ + hoistFunctionDeclaration(node: FunctionDeclaration): void; + + /** + * Hoists a variable declaration to the containing scope. + */ + hoistVariableDeclaration(node: Identifier): void; + + /** + * Records a request for a non-scoped emit helper in the current context. + */ + requestEmitHelper(helper: EmitHelper): void; + + /** + * Gets and resets the requested non-scoped emit helpers. + */ + readEmitHelpers(): EmitHelper[] | undefined; + + /** + * 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 79e15566e1d..5bdcf27769f 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 { @@ -595,8 +580,9 @@ namespace ts { return n.kind === SyntaxKind.CallExpression && (n).expression.kind === SyntaxKind.SuperKeyword; } - export function isPrologueDirective(node: Node): boolean { - return node.kind === SyntaxKind.ExpressionStatement && (node).expression.kind === SyntaxKind.StringLiteral; + export function isPrologueDirective(node: Node): node is PrologueDirective { + return node.kind === SyntaxKind.ExpressionStatement + && (node).expression.kind === SyntaxKind.StringLiteral; } export function getLeadingCommentRangesOfNode(node: Node, sourceFileOfNode: SourceFile) { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 609031a8221..dc0e69a9b0f 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -653,6 +653,53 @@ 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): ConciseBody { + context.resumeLexicalEnvironment(); + const updated = visitNode(node, visitor, isConciseBody, optional); + const declarations = context.endLexicalEnvironment(); + if (some(declarations)) { + const block = convertToFunctionBody(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 +707,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 +761,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, /*optional*/ true)); 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 +849,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, /*optional*/ true)); 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 +1030,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, @@ -1129,11 +1162,7 @@ namespace ts { 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: @@ -1167,6 +1196,24 @@ namespace ts { // return node; } + /** + * Merges generated lexical declarations into a new statement list. + */ + export function mergeLexicalEnvironment(statements: NodeArray, declarations: Statement[]): NodeArray; + /** + * Appends generated lexical declarations to an array of statements. + */ + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]): Statement[]; + export function mergeLexicalEnvironment(statements: Statement[], declarations: Statement[]) { + if (!some(declarations)) { + return statements; + } + return isNodeArray(statements) + ? createNodeArray(concatenate(statements, declarations), statements) + : addRange(statements, declarations); + } + + /** * Merges generated lexical declarations into the FunctionBody of a non-arrow function-like declaration. *