diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ebcea221f9e8c..761253439fa5b 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1754,6 +1754,23 @@ namespace ts { // Utilities + export function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { + if (!outermostLabeledStatement) { + return node; + } + const updated = updateLabel( + outermostLabeledStatement, + outermostLabeledStatement.label, + outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement + ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) + : node + ); + if (afterRestoreLabelCallback) { + afterRestoreLabelCallback(outermostLabeledStatement); + } + return updated; + } + export interface CallBinding { target: LeftHandSideExpression; thisArg: Expression; diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 5dae1da7b742b..774c6db2cc472 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -162,6 +162,102 @@ namespace ts { ReplaceWithReturn, } + type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]) => Statement; + + // Facts we track as we traverse the tree + const enum HierarchyFacts { + None = 0, + + // + // Ancestor facts + // + + Function = 1 << 0, // Enclosed in a non-arrow function + ArrowFunction = 1 << 1, // Enclosed in an arrow function + AsyncFunctionBody = 1 << 2, // Enclosed in an async function body + NonStaticClassElement = 1 << 3, // Enclosed in a non-static, non-async class element + CapturesThis = 1 << 4, // Enclosed in a function that captures the lexical 'this' (used in substitution) + ExportedVariableStatement = 1 << 5, // Enclosed in an exported variable statement in the current scope + TopLevel = 1 << 6, // Enclosing block-scoped container is a top-level container + Block = 1 << 7, // Enclosing block-scoped container is a Block + IterationStatement = 1 << 8, // Enclosed in an IterationStatement + IterationStatementBlock = 1 << 9, // Enclosing Block is enclosed in an IterationStatement + ForStatement = 1 << 10, // Enclosing block-scoped container is a ForStatement + ForInOrForOfStatement = 1 << 11, // Enclosing block-scoped container is a ForInStatement or ForOfStatement + ConstructorWithCapturedSuper = 1 << 12, // Enclosed in a constructor that captures 'this' for use with 'super' + ComputedPropertyName = 1 << 13, // Enclosed in a computed property name + // NOTE: do not add more ancestor flags without also updating AncestorFactsMask below. + + // + // Ancestor masks + // + + AncestorFactsMask = (ComputedPropertyName << 1) - 1, + + // We are always in *some* kind of block scope, but only specific block-scope containers are + // top-level or Blocks. + BlockScopeIncludes = None, + BlockScopeExcludes = TopLevel | Block | IterationStatement | IterationStatementBlock | ForStatement | ForInOrForOfStatement, + + // A source file is a top-level block scope. + SourceFileIncludes = TopLevel, + SourceFileExcludes = BlockScopeExcludes & ~TopLevel, + + // Functions, methods, and accessors are both new lexical scopes and new block scopes. + FunctionIncludes = Function | TopLevel, + FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper | ComputedPropertyName, + + AsyncFunctionBodyIncludes = FunctionIncludes | AsyncFunctionBody, + AsyncFunctionBodyExcludes = FunctionExcludes & ~NonStaticClassElement, + + // Arrow functions are lexically scoped to their container, but are new block scopes. + ArrowFunctionIncludes = ArrowFunction | TopLevel, + ArrowFunctionExcludes = BlockScopeExcludes & ~TopLevel | ConstructorWithCapturedSuper | ComputedPropertyName, + + // Constructors are both new lexical scopes and new block scopes. Constructors are also + // always considered non-static members of a class. + ConstructorIncludes = FunctionIncludes | NonStaticClassElement, + ConstructorExcludes = FunctionExcludes & ~NonStaticClassElement, + + // 'do' and 'while' statements are not block scopes. We track that the subtree is contained + // within an IterationStatement to indicate whether the embedded statement is an + // IterationStatementBlock. + DoOrWhileStatementIncludes = IterationStatement, + DoOrWhileStatementExcludes = None, + + // 'for' statements are new block scopes and have special handling for 'let' declarations. + ForStatementIncludes = IterationStatement | ForStatement, + ForStatementExcludes = BlockScopeExcludes & ~ForStatement, + + // 'for-in' and 'for-of' statements are new block scopes and have special handling for + // 'let' declarations. + ForInOrForOfStatementIncludes = IterationStatement | ForInOrForOfStatement, + ForInOrForOfStatementExcludes = BlockScopeExcludes & ~ForInOrForOfStatement, + + // Blocks (other than function bodies) are new block scopes. + BlockIncludes = Block, + BlockExcludes = BlockScopeExcludes & ~Block, + + IterationStatementBlockIncludes = IterationStatementBlock, + IterationStatementBlockExcludes = BlockScopeExcludes, + + // Computed property names track subtree flags differently than their containing members. + ComputedPropertyNameIncludes = ComputedPropertyName, + ComputedPropertyNameExcludes = None, + + // + // Subtree facts + // + + // NOTE: To be added in a later PR + + // + // Subtree masks + // + + SubtreeFactsMask = ~AncestorFactsMask, + } + export function transformES2015(context: TransformationContext) { const { startLexicalEnvironment, @@ -178,15 +274,7 @@ namespace ts { let currentSourceFile: SourceFile; let currentText: string; - let currentParent: Node; - let currentNode: Node; - let enclosingVariableStatement: VariableStatement; - let enclosingBlockScopeContainer: Node; - let enclosingBlockScopeContainerParent: Node; - let enclosingFunction: FunctionLikeDeclaration; - let enclosingNonArrowFunction: FunctionLikeDeclaration; - let enclosingNonAsyncFunctionBody: FunctionLikeDeclaration | ClassElement; - let isInConstructorWithCapturedSuper: boolean; + let hierarchyFacts: HierarchyFacts; /** * Used to track if we are emitting body of the converted loop @@ -210,166 +298,71 @@ namespace ts { currentSourceFile = node; currentText = node.text; - const visited = saveStateAndInvoke(node, visitSourceFile); + const visited = visitSourceFile(node); addEmitHelpers(visited, context.readEmitHelpers()); currentSourceFile = undefined; currentText = undefined; + hierarchyFacts = HierarchyFacts.None; return visited; } - function visitor(node: Node): VisitResult { - return saveStateAndInvoke(node, dispatcher); - } - - function dispatcher(node: Node): VisitResult { - return convertedLoopState - ? visitorForConvertedLoopWorker(node) - : visitorWorker(node); - } - - function saveStateAndInvoke(node: T, f: (node: T) => U): U { - const savedEnclosingFunction = enclosingFunction; - const savedEnclosingNonArrowFunction = enclosingNonArrowFunction; - const savedEnclosingNonAsyncFunctionBody = enclosingNonAsyncFunctionBody; - const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer; - const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent; - const savedEnclosingVariableStatement = enclosingVariableStatement; - const savedCurrentParent = currentParent; - const savedCurrentNode = currentNode; - const savedConvertedLoopState = convertedLoopState; - const savedIsInConstructorWithCapturedSuper = isInConstructorWithCapturedSuper; - if (nodeStartsNewLexicalEnvironment(node)) { - // don't treat content of nodes that start new lexical environment as part of converted loop copy or constructor body - isInConstructorWithCapturedSuper = false; - convertedLoopState = undefined; - } - - onBeforeVisitNode(node); - const visited = f(node); - - isInConstructorWithCapturedSuper = savedIsInConstructorWithCapturedSuper; - convertedLoopState = savedConvertedLoopState; - enclosingFunction = savedEnclosingFunction; - enclosingNonArrowFunction = savedEnclosingNonArrowFunction; - enclosingNonAsyncFunctionBody = savedEnclosingNonAsyncFunctionBody; - enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer; - enclosingBlockScopeContainerParent = savedEnclosingBlockScopeContainerParent; - enclosingVariableStatement = savedEnclosingVariableStatement; - currentParent = savedCurrentParent; - currentNode = savedCurrentNode; - 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; + /** + * Sets the `HierarchyFacts` for this node prior to visiting this node's subtree, returning the facts set prior to modification. + * @param excludeFacts The existing `HierarchyFacts` to reset before visiting the subtree. + * @param includeFacts The new `HierarchyFacts` to set before visiting the subtree. + **/ + function enterSubtree(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts) { + const ancestorFacts = hierarchyFacts; + hierarchyFacts = (hierarchyFacts & ~excludeFacts | includeFacts) & HierarchyFacts.AncestorFactsMask; + return ancestorFacts; } - function returnCapturedThis(node: Node): Node { - return setOriginalNode(createReturn(createIdentifier("_this")), node); + /** + * Restores the `HierarchyFacts` for this node's ancestor after visiting this node's + * subtree, propagating specific facts from the subtree. + * @param ancestorFacts The `HierarchyFacts` of the ancestor to restore after visiting the subtree. + * @param excludeFacts The existing `HierarchyFacts` of the subtree that should not be propagated. + * @param includeFacts The new `HierarchyFacts` of the subtree that should be propagated. + **/ + function exitSubtree(ancestorFacts: HierarchyFacts, excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts) { + hierarchyFacts = (hierarchyFacts & ~excludeFacts | includeFacts) & HierarchyFacts.SubtreeFactsMask | ancestorFacts; } function isReturnVoidStatementInConstructorWithCapturedSuper(node: Node): boolean { - return isInConstructorWithCapturedSuper && node.kind === SyntaxKind.ReturnStatement && !(node).expression; + return hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper + && node.kind === SyntaxKind.ReturnStatement + && !(node).expression; } - function shouldCheckNode(node: Node): boolean { - return (node.transformFlags & TransformFlags.ES2015) !== 0 || - node.kind === SyntaxKind.LabeledStatement || - (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)); + function shouldVisitNode(node: Node): boolean { + return (node.transformFlags & TransformFlags.ContainsES2015) !== 0 + || convertedLoopState !== undefined + || (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatement(node)) + || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)); } - function visitorWorker(node: Node): VisitResult { - if (isReturnVoidStatementInConstructorWithCapturedSuper(node)) { - return returnCapturedThis(node); - } - else if (shouldCheckNode(node)) { + function visitor(node: Node): VisitResult { + if (shouldVisitNode(node)) { return visitJavaScript(node); } - 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 - return visitEachChild(node, visitor, context); - } else { return node; } } - function visitorForConvertedLoopWorker(node: Node): VisitResult { - let result: VisitResult; - if (shouldCheckNode(node)) { - result = visitJavaScript(node); + function functionBodyVisitor(node: Block): Block { + if (shouldVisitNode(node)) { + return visitBlock(node, /*isFunctionBody*/ true); } - else { - result = visitNodesInConvertedLoop(node); - } - return result; + return node; } - function visitNodesInConvertedLoop(node: Node): VisitResult { - switch (node.kind) { - case SyntaxKind.ReturnStatement: - node = isReturnVoidStatementInConstructorWithCapturedSuper(node) ? returnCapturedThis(node) : node; - return visitReturnStatement(node); - - case SyntaxKind.VariableStatement: - return visitVariableStatement(node); - - case SyntaxKind.SwitchStatement: - return visitSwitchStatement(node); - - case SyntaxKind.BreakStatement: - case SyntaxKind.ContinueStatement: - return visitBreakOrContinueStatement(node); - - case SyntaxKind.ThisKeyword: - return visitThisKeyword(node); - - case SyntaxKind.Identifier: - return visitIdentifier(node); - - default: - return visitEachChild(node, visitor, context); + function callExpressionVisitor(node: Node): VisitResult { + if (node.kind === SyntaxKind.SuperKeyword) { + return visitSuperKeyword(/*isExpressionOfCall*/ true); } + return visitor(node); } function visitJavaScript(node: Node): VisitResult { @@ -404,23 +397,34 @@ namespace ts { case SyntaxKind.VariableDeclarationList: return visitVariableDeclarationList(node); + case SyntaxKind.SwitchStatement: + return visitSwitchStatement(node); + + case SyntaxKind.CaseBlock: + return visitCaseBlock(node); + + case SyntaxKind.Block: + return visitBlock(node, /*isFunctionBody*/ false); + + case SyntaxKind.BreakStatement: + case SyntaxKind.ContinueStatement: + return visitBreakOrContinueStatement(node); + case SyntaxKind.LabeledStatement: return visitLabeledStatement(node); case SyntaxKind.DoStatement: - return visitDoStatement(node); - case SyntaxKind.WhileStatement: - return visitWhileStatement(node); + return visitDoOrWhileStatement(node, /*outermostLabeledStatement*/ undefined); case SyntaxKind.ForStatement: - return visitForStatement(node); + return visitForStatement(node, /*outermostLabeledStatement*/ undefined); case SyntaxKind.ForInStatement: - return visitForInStatement(node); + return visitForInStatement(node, /*outermostLabeledStatement*/ undefined); case SyntaxKind.ForOfStatement: - return visitForOfStatement(node); + return visitForOfStatement(node, /*outermostLabeledStatement*/ undefined); case SyntaxKind.ExpressionStatement: return visitExpressionStatement(node); @@ -434,6 +438,9 @@ namespace ts { case SyntaxKind.ShorthandPropertyAssignment: return visitShorthandPropertyAssignment(node); + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node); + case SyntaxKind.ArrayLiteralExpression: return visitArrayLiteralExpression(node); @@ -468,31 +475,38 @@ namespace ts { return visitSpreadElement(node); case SyntaxKind.SuperKeyword: - return visitSuperKeyword(); + return visitSuperKeyword(/*isExpressionOfCall*/ false); - case SyntaxKind.YieldExpression: - // `yield` will be handled by a generators transform. - return visitEachChild(node, visitor, context); + case SyntaxKind.ThisKeyword: + return visitThisKeyword(node); case SyntaxKind.MethodDeclaration: return visitMethodDeclaration(node); + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + return visitAccessorDeclaration(node); + case SyntaxKind.VariableStatement: return visitVariableStatement(node); + case SyntaxKind.ReturnStatement: + return visitReturnStatement(node); + default: - Debug.failBadSyntaxKind(node); return visitEachChild(node, visitor, context); } } function visitSourceFile(node: SourceFile): SourceFile { + const ancestorFacts = enterSubtree(HierarchyFacts.SourceFileExcludes, HierarchyFacts.SourceFileIncludes); 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()); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updateSourceFileNode( node, createNodeArray(statements, node.statements) @@ -500,44 +514,63 @@ namespace ts { } function visitSwitchStatement(node: SwitchStatement): SwitchStatement { - Debug.assert(convertedLoopState !== undefined); - - const savedAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps; - // for switch statement allow only non-labeled break - convertedLoopState.allowedNonLabeledJumps |= Jump.Break; + if (convertedLoopState !== undefined) { + const savedAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps; + // for switch statement allow only non-labeled break + convertedLoopState.allowedNonLabeledJumps |= Jump.Break; + const result = visitEachChild(node, visitor, context); + convertedLoopState.allowedNonLabeledJumps = savedAllowedNonLabeledJumps; + return result; + } + return visitEachChild(node, visitor, context); + } - const result = visitEachChild(node, visitor, context); + function visitCaseBlock(node: CaseBlock): CaseBlock { + const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); + const updated = visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; + } - convertedLoopState.allowedNonLabeledJumps = savedAllowedNonLabeledJumps; - return result; + function returnCapturedThis(node: Node): ReturnStatement { + return setOriginalNode(createReturn(createIdentifier("_this")), node); } function visitReturnStatement(node: ReturnStatement): Statement { - Debug.assert(convertedLoopState !== undefined); - - convertedLoopState.nonLocalJumps |= Jump.Return; - return createReturn( - createObjectLiteral( - [ - createPropertyAssignment( - createIdentifier("value"), - node.expression - ? visitNode(node.expression, visitor, isExpression) - : createVoidZero() - ) - ] - ) - ); + if (convertedLoopState) { + convertedLoopState.nonLocalJumps |= Jump.Return; + if (isReturnVoidStatementInConstructorWithCapturedSuper(node)) { + node = returnCapturedThis(node); + } + return createReturn( + createObjectLiteral( + [ + createPropertyAssignment( + createIdentifier("value"), + node.expression + ? visitNode(node.expression, visitor, isExpression) + : createVoidZero() + ) + ] + ) + ); + } + else if (isReturnVoidStatementInConstructorWithCapturedSuper(node)) { + return returnCapturedThis(node); + } + return visitEachChild(node, visitor, context); } function visitThisKeyword(node: Node): Node { - Debug.assert(convertedLoopState !== undefined); - if (enclosingFunction && enclosingFunction.kind === SyntaxKind.ArrowFunction) { - // if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword. - convertedLoopState.containsLexicalThis = true; - return node; + if (convertedLoopState) { + if (hierarchyFacts & HierarchyFacts.ArrowFunction) { + // if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword. + convertedLoopState.containsLexicalThis = true; + return node; + } + return convertedLoopState.thisName || (convertedLoopState.thisName = createUniqueName("this")); } - return convertedLoopState.thisName || (convertedLoopState.thisName = createUniqueName("this")); + return node; } function visitIdentifier(node: Identifier): Identifier { @@ -811,9 +844,11 @@ namespace ts { * @param extendsClauseElement The expression for the class `extends` clause. */ function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, extendsClauseElement: ExpressionWithTypeArguments): void { + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const ancestorFacts = enterSubtree(HierarchyFacts.ConstructorExcludes, HierarchyFacts.ConstructorIncludes); const constructor = getFirstConstructorWithBody(node); const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, extendsClauseElement !== undefined); - const constructorFunction = createFunctionDeclaration( /*decorators*/ undefined, @@ -830,7 +865,10 @@ namespace ts { if (extendsClauseElement) { setEmitFlags(constructorFunction, EmitFlags.CapturesThis); } + statements.push(constructorFunction); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; } /** @@ -890,11 +928,11 @@ namespace ts { } if (constructor) { - const body = saveStateAndInvoke(constructor, constructor => { - isInConstructorWithCapturedSuper = superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture; - return visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset); - }); - addRange(statements, body); + if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture) { + hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; + } + + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); } // Return `_this` unless we're sure enough that it would be pointless to add a return statement. @@ -1016,11 +1054,7 @@ namespace ts { firstStatement = ctorStatements[statementOffset]; if (firstStatement.kind === SyntaxKind.ExpressionStatement && isSuperCall((firstStatement as ExpressionStatement).expression)) { - const superCall = (firstStatement as ExpressionStatement).expression as CallExpression; - superCallExpression = setOriginalNode( - saveStateAndInvoke(superCall, visitImmediateSuperCallInBody), - superCall - ); + superCallExpression = visitImmediateSuperCallInBody((firstStatement as ExpressionStatement).expression as CallExpression); } } @@ -1372,14 +1406,14 @@ namespace ts { break; case SyntaxKind.MethodDeclaration: - statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), member)); + statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), member, node)); break; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: const accessors = getAllAccessorDeclarations(node.members, member); if (member === accessors.firstAccessor) { - statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors)); + statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors, node)); } break; @@ -1410,11 +1444,12 @@ namespace ts { * @param receiver The receiver for the member. * @param member The MethodDeclaration node. */ - function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) { + function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration, container: Node) { + const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None); const commentRange = getCommentRange(member); const sourceMapRange = getSourceMapRange(member); const memberName = createMemberAccessForPropertyName(receiver, visitNode(member.name, visitor, isPropertyName), /*location*/ member.name); - const memberFunction = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined); + const memberFunction = transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined, container); setEmitFlags(memberFunction, EmitFlags.NoComments); setSourceMapRange(memberFunction, sourceMapRange); @@ -1430,6 +1465,8 @@ namespace ts { // No source map should be emitted for this statement to align with the // old emitter. setEmitFlags(statement, EmitFlags.NoSourceMap); + + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return statement; } @@ -1439,9 +1476,9 @@ namespace ts { * @param receiver The receiver for the member. * @param accessors The set of related get/set accessors. */ - function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations): Statement { + function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations, container: Node): Statement { const statement = createStatement( - transformAccessorsToExpression(receiver, accessors, /*startsOnNewLine*/ false), + transformAccessorsToExpression(receiver, accessors, container, /*startsOnNewLine*/ false), /*location*/ getSourceMapRange(accessors.firstAccessor) ); @@ -1458,7 +1495,9 @@ namespace ts { * * @param receiver The receiver for the member. */ - function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations, startsOnNewLine: boolean): Expression { + function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations, container: Node, startsOnNewLine: boolean): Expression { + const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None); + // To align with source maps in the old emitter, the receiver and property name // arguments are both mapped contiguously to the accessor name. const target = getMutableClone(receiver); @@ -1471,7 +1510,7 @@ namespace ts { const properties: ObjectLiteralElementLike[] = []; if (getAccessor) { - const getterFunction = transformFunctionLikeToExpression(getAccessor, /*location*/ undefined, /*name*/ undefined); + const getterFunction = transformFunctionLikeToExpression(getAccessor, /*location*/ undefined, /*name*/ undefined, container); setSourceMapRange(getterFunction, getSourceMapRange(getAccessor)); setEmitFlags(getterFunction, EmitFlags.NoLeadingComments); const getter = createPropertyAssignment("get", getterFunction); @@ -1480,7 +1519,7 @@ namespace ts { } if (setAccessor) { - const setterFunction = transformFunctionLikeToExpression(setAccessor, /*location*/ undefined, /*name*/ undefined); + const setterFunction = transformFunctionLikeToExpression(setAccessor, /*location*/ undefined, /*name*/ undefined, container); setSourceMapRange(setterFunction, getSourceMapRange(setAccessor)); setEmitFlags(setterFunction, EmitFlags.NoLeadingComments); const setter = createPropertyAssignment("set", setterFunction); @@ -1505,6 +1544,8 @@ namespace ts { if (startsOnNewLine) { call.startsOnNewLine = true; } + + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return call; } @@ -1517,6 +1558,9 @@ namespace ts { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { enableSubstitutionsForCapturedThis(); } + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const ancestorFacts = enterSubtree(HierarchyFacts.ArrowFunctionExcludes, HierarchyFacts.ArrowFunctionIncludes); const func = createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, @@ -1529,6 +1573,8 @@ namespace ts { ); setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; return func; } @@ -1538,7 +1584,12 @@ namespace ts { * @param node a FunctionExpression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - return updateFunctionExpression( + const ancestorFacts = getEmitFlags(node) & EmitFlags.AsyncFunctionBody + ? enterSubtree(HierarchyFacts.AsyncFunctionBodyExcludes, HierarchyFacts.AsyncFunctionBodyIncludes) + : enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const updated = updateFunctionExpression( node, /*modifiers*/ undefined, node.name, @@ -1547,8 +1598,11 @@ namespace ts { /*type*/ undefined, node.transformFlags & TransformFlags.ES2015 ? transformFunctionBody(node) - : visitFunctionBody(node.body, visitor, context) + : visitFunctionBody(node.body, functionBodyVisitor, context) ); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; + return updated; } /** @@ -1557,18 +1611,25 @@ namespace ts { * @param node a FunctionDeclaration node. */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - return updateFunctionDeclaration( + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); + const updated = updateFunctionDeclaration( node, /*decorators*/ undefined, - node.modifiers, + visitNodes(node.modifiers, visitor, isModifier), node.name, /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, node.transformFlags & TransformFlags.ES2015 ? transformFunctionBody(node) - : visitFunctionBody(node.body, visitor, context) + : visitFunctionBody(node.body, functionBodyVisitor, context) ); + + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; + return updated; } /** @@ -1578,12 +1639,12 @@ namespace ts { * @param location The source-map location for the new FunctionExpression. * @param name The name of the new FunctionExpression. */ - function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location: TextRange, name: Identifier): FunctionExpression { - const savedContainingNonArrowFunction = enclosingNonArrowFunction; - if (node.kind !== SyntaxKind.ArrowFunction) { - enclosingNonArrowFunction = node; - } - + function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location: TextRange, name: Identifier, container: Node): FunctionExpression { + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const ancestorFacts = container && isClassLike(container) && !hasModifier(node, ModifierFlags.Static) + ? enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes | HierarchyFacts.NonStaticClassElement) + : enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); const expression = setOriginalNode( createFunctionExpression( /*modifiers*/ undefined, @@ -1592,13 +1653,13 @@ namespace ts { /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - saveStateAndInvoke(node, transformFunctionBody), + transformFunctionBody(node), location ), /*original*/ node ); - - enclosingNonArrowFunction = savedContainingNonArrowFunction; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; return expression; } @@ -1692,6 +1753,19 @@ namespace ts { return block; } + function visitBlock(node: Block, isFunctionBody: boolean): Block { + if (isFunctionBody) { + // A function body is not a block scope. + return visitEachChild(node, visitor, context); + } + const ancestorFacts = hierarchyFacts & HierarchyFacts.IterationStatement + ? enterSubtree(HierarchyFacts.IterationStatementBlockExcludes, HierarchyFacts.IterationStatementBlockIncludes) + : enterSubtree(HierarchyFacts.BlockExcludes, HierarchyFacts.BlockIncludes); + const updated = visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; + } + /** * Visits an ExpressionStatement that contains a destructuring assignment. * @@ -1745,10 +1819,13 @@ namespace ts { FlattenLevel.All, needsDestructuringValue); } + return visitEachChild(node, visitor, context); } function visitVariableStatement(node: VariableStatement): Statement { - if (convertedLoopState && (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) == 0) { + const ancestorFacts = enterSubtree(HierarchyFacts.None, hasModifier(node, ModifierFlags.Export) ? HierarchyFacts.ExportedVariableStatement : HierarchyFacts.None); + let updated: Statement; + if (convertedLoopState && (node.declarationList.flags & NodeFlags.BlockScoped) === 0) { // we are inside a converted loop - hoist variable declarations let assignments: Expression[]; for (const decl of node.declarationList.declarations) { @@ -1770,15 +1847,19 @@ namespace ts { } } if (assignments) { - return createStatement(reduceLeft(assignments, (acc, v) => createBinary(v, SyntaxKind.CommaToken, acc)), node); + updated = createStatement(reduceLeft(assignments, (acc, v) => createBinary(v, SyntaxKind.CommaToken, acc)), node); } else { // none of declarations has initializer - the entire variable statement can be deleted - return undefined; + updated = undefined; } } + else { + updated = visitEachChild(node, visitor, context); + } - return visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; } /** @@ -1787,29 +1868,32 @@ namespace ts { * @param node A VariableDeclarationList node. */ function visitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList { - if (node.flags & NodeFlags.BlockScoped) { - enableSubstitutionsForBlockScopedBindings(); - } + if (node.transformFlags & TransformFlags.ES2015) { + if (node.flags & NodeFlags.BlockScoped) { + enableSubstitutionsForBlockScopedBindings(); + } - const declarations = flatten(map(node.declarations, node.flags & NodeFlags.Let - ? visitVariableDeclarationInLetDeclarationList - : visitVariableDeclaration)); + const declarations = flatten(map(node.declarations, node.flags & NodeFlags.Let + ? visitVariableDeclarationInLetDeclarationList + : visitVariableDeclaration)); - const declarationList = createVariableDeclarationList(declarations, /*location*/ node); - setOriginalNode(declarationList, node); - setCommentRange(declarationList, node); + const declarationList = createVariableDeclarationList(declarations, /*location*/ node); + setOriginalNode(declarationList, node); + setCommentRange(declarationList, node); - if (node.transformFlags & TransformFlags.ContainsBindingPattern - && (isBindingPattern(node.declarations[0].name) - || isBindingPattern(lastOrUndefined(node.declarations).name))) { - // If the first or last declaration is a binding pattern, we need to modify - // the source map range for the declaration list. - const firstDeclaration = firstOrUndefined(declarations); - const lastDeclaration = lastOrUndefined(declarations); - setSourceMapRange(declarationList, createRange(firstDeclaration.pos, lastDeclaration.end)); - } + if (node.transformFlags & TransformFlags.ContainsBindingPattern + && (isBindingPattern(node.declarations[0].name) + || isBindingPattern(lastOrUndefined(node.declarations).name))) { + // If the first or last declaration is a binding pattern, we need to modify + // the source map range for the declaration list. + const firstDeclaration = firstOrUndefined(declarations); + const lastDeclaration = lastOrUndefined(declarations); + setSourceMapRange(declarationList, createRange(firstDeclaration.pos, lastDeclaration.end)); + } - return declarationList; + return declarationList; + } + return visitEachChild(node, visitor, context); } /** @@ -1863,20 +1947,18 @@ namespace ts { const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; const emittedAsTopLevel = - isBlockScopedContainerTopLevel(enclosingBlockScopeContainer) + (hierarchyFacts & HierarchyFacts.TopLevel) !== 0 || (isCapturedInFunction && isDeclaredInLoop - && isBlock(enclosingBlockScopeContainer) - && isIterationStatement(enclosingBlockScopeContainerParent, /*lookInLabeledStatements*/ false)); + && (hierarchyFacts & HierarchyFacts.IterationStatementBlock) !== 0); const emitExplicitInitializer = !emittedAsTopLevel - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForInStatement - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForOfStatement + && (hierarchyFacts & HierarchyFacts.ForInOrForOfStatement) === 0 && (!resolver.isDeclarationWithCollidingName(node) || (isDeclaredInLoop && !isCapturedInFunction - && !isIterationStatement(enclosingBlockScopeContainer, /*lookInLabeledStatements*/ false))); + && (hierarchyFacts & (HierarchyFacts.ForStatement | HierarchyFacts.ForInOrForOfStatement)) === 0)); return emitExplicitInitializer; } @@ -1910,72 +1992,85 @@ namespace ts { * @param node A VariableDeclaration node. */ function visitVariableDeclaration(node: VariableDeclaration): VisitResult { - // If we are here it is because the name contains a binding pattern. + const ancestorFacts = enterSubtree(HierarchyFacts.ExportedVariableStatement, HierarchyFacts.None); + let updated: VisitResult; if (isBindingPattern(node.name)) { - const hoistTempVariables = enclosingVariableStatement - && hasModifier(enclosingVariableStatement, ModifierFlags.Export); - return flattenDestructuringBinding( + updated = flattenDestructuringBinding( node, visitor, context, FlattenLevel.All, /*value*/ undefined, - hoistTempVariables + (ancestorFacts & HierarchyFacts.ExportedVariableStatement) !== 0 ); } + else { + updated = visitEachChild(node, visitor, context); + } - return visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; } - function visitLabeledStatement(node: LabeledStatement): VisitResult { - if (convertedLoopState) { - if (!convertedLoopState.labels) { - convertedLoopState.labels = createMap(); - } - convertedLoopState.labels[node.label.text] = node.label.text; - } + function recordLabel(node: LabeledStatement) { + convertedLoopState.labels[node.label.text] = node.label.text; + } - let result: VisitResult; - if (isIterationStatement(node.statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node.statement)) { - result = visitNodes(createNodeArray([node.statement]), visitor, isStatement); - } - else { - result = visitEachChild(node, visitor, context); - } + function resetLabel(node: LabeledStatement) { + convertedLoopState.labels[node.label.text] = undefined; + } - if (convertedLoopState) { - convertedLoopState.labels[node.label.text] = undefined; + function visitLabeledStatement(node: LabeledStatement): VisitResult { + if (convertedLoopState && !convertedLoopState.labels) { + convertedLoopState.labels = createMap(); } - - return result; + const statement = unwrapInnermostStatmentOfLabel(node, convertedLoopState && recordLabel); + return isIterationStatement(statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(statement) + ? visitIterationStatement(statement, /*outermostLabeledStatement*/ node) + : restoreEnclosingLabel(visitNode(statement, visitor, isStatement), node, convertedLoopState && resetLabel); } - function visitDoStatement(node: DoStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitIterationStatementWithFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { + const ancestorFacts = enterSubtree(excludeFacts, includeFacts); + const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; } - function visitWhileStatement(node: WhileStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitDoOrWhileStatement(node: DoStatement | WhileStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + HierarchyFacts.DoOrWhileStatementExcludes, + HierarchyFacts.DoOrWhileStatementIncludes, + node, + outermostLabeledStatement); } - function visitForStatement(node: ForStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitForStatement(node: ForStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + HierarchyFacts.ForStatementExcludes, + HierarchyFacts.ForStatementIncludes, + node, + outermostLabeledStatement); } - function visitForInStatement(node: ForInStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitForInStatement(node: ForInStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, + node, + outermostLabeledStatement); } - /** - * Visits a ForOfStatement and converts it into a compatible ForStatement. - * - * @param node A ForOfStatement. - */ - function visitForOfStatement(node: ForOfStatement): VisitResult { - return convertIterationStatementBodyIfNecessary(node, convertForOfToFor); + function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement): VisitResult { + return visitIterationStatementWithFacts( + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, + node, + outermostLabeledStatement, + convertForOfToFor); } - function convertForOfToFor(node: ForOfStatement, convertedLoopBodyStatements: Statement[]): ForStatement { + function convertForOfToFor(node: ForOfStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]): Statement { // The following ES6 code: // // for (let v of expr) { } @@ -2142,7 +2237,21 @@ 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 restoreEnclosingLabel(forStatement, outermostLabeledStatement, convertedLoopState && resetLabel); + } + + function visitIterationStatement(node: IterationStatement, outermostLabeledStatement: LabeledStatement) { + switch (node.kind) { + case SyntaxKind.DoStatement: + case SyntaxKind.WhileStatement: + return visitDoOrWhileStatement(node, outermostLabeledStatement); + case SyntaxKind.ForStatement: + return visitForStatement(node, outermostLabeledStatement); + case SyntaxKind.ForInStatement: + return visitForInStatement(node, outermostLabeledStatement); + case SyntaxKind.ForOfStatement: + return visitForOfStatement(node, outermostLabeledStatement); + } } /** @@ -2158,45 +2267,56 @@ namespace ts { // Find the first computed property. // Everything until that point can be emitted as part of the initial object literal. let numInitialProperties = numProperties; + let numInitialPropertiesWithoutYield = numProperties; for (let i = 0; i < numProperties; i++) { const property = properties[i]; - if (property.transformFlags & TransformFlags.ContainsYield - || property.name.kind === SyntaxKind.ComputedPropertyName) { + if ((property.transformFlags & TransformFlags.ContainsYield && hierarchyFacts & HierarchyFacts.AsyncFunctionBody) + && i < numInitialPropertiesWithoutYield) { + numInitialPropertiesWithoutYield = i; + } + if (property.name.kind === SyntaxKind.ComputedPropertyName) { numInitialProperties = i; break; } } - Debug.assert(numInitialProperties !== numProperties); + if (numInitialProperties !== numProperties) { + if (numInitialPropertiesWithoutYield < numInitialProperties) { + numInitialProperties = numInitialPropertiesWithoutYield; + } - // For computed properties, we need to create a unique handle to the object - // literal so we can modify it without risking internal assignments tainting the object. - const temp = createTempVariable(hoistVariableDeclaration); + // For computed properties, we need to create a unique handle to the object + // literal so we can modify it without risking internal assignments tainting the object. + const temp = createTempVariable(hoistVariableDeclaration); - // Write out the first non-computed properties, then emit the rest through indexing on the temp variable. - const expressions: Expression[] = []; - const assignment = createAssignment( - temp, - setEmitFlags( - createObjectLiteral( - visitNodes(properties, visitor, isObjectLiteralElementLike, 0, numInitialProperties), - /*location*/ undefined, - node.multiLine - ), - EmitFlags.Indented - ) - ); - if (node.multiLine) { - assignment.startsOnNewLine = true; - } - expressions.push(assignment); + // Write out the first non-computed properties, then emit the rest through indexing on the temp variable. + const expressions: Expression[] = []; + const assignment = createAssignment( + temp, + setEmitFlags( + createObjectLiteral( + visitNodes(properties, visitor, isObjectLiteralElementLike, 0, numInitialProperties), + /*location*/ undefined, + node.multiLine + ), + EmitFlags.Indented + ) + ); + + if (node.multiLine) { + assignment.startsOnNewLine = true; + } + + expressions.push(assignment); - addObjectLiteralMembers(expressions, node, temp, numInitialProperties); + addObjectLiteralMembers(expressions, node, temp, numInitialProperties); - // We need to clone the temporary identifier so that we can write it on a - // new line - expressions.push(node.multiLine ? startOnNewLine(getMutableClone(temp)) : temp); - return inlineExpressions(expressions); + // We need to clone the temporary identifier so that we can write it on a + // new line + expressions.push(node.multiLine ? startOnNewLine(getMutableClone(temp)) : temp); + return inlineExpressions(expressions); + } + return visitEachChild(node, visitor, context); } function shouldConvertIterationStatementBody(node: IterationStatement): boolean { @@ -2227,7 +2347,7 @@ namespace ts { } } - function convertIterationStatementBodyIfNecessary(node: IterationStatement, convert?: (node: IterationStatement, convertedLoopBodyStatements: Statement[]) => IterationStatement): VisitResult { + function convertIterationStatementBodyIfNecessary(node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter): VisitResult { if (!shouldConvertIterationStatementBody(node)) { let saveAllowedNonLabeledJumps: Jump; if (convertedLoopState) { @@ -2237,7 +2357,9 @@ namespace ts { convertedLoopState.allowedNonLabeledJumps = Jump.Break | Jump.Continue; } - const result = convert ? convert(node, /*convertedLoopBodyStatements*/ undefined) : visitEachChild(node, visitor, context); + const result = convert + ? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined) + : restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement, convertedLoopState && resetLabel); if (convertedLoopState) { convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps; @@ -2313,8 +2435,7 @@ namespace ts { } const isAsyncBlockContainingAwait = - enclosingNonArrowFunction - && (getEmitFlags(enclosingNonArrowFunction) & EmitFlags.AsyncFunctionBody) !== 0 + hierarchyFacts & HierarchyFacts.AsyncFunctionBody && (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0; let loopBodyFlags: EmitFlags = 0; @@ -2433,34 +2554,30 @@ namespace ts { } const convertedLoopBodyStatements = generateCallToConvertedLoop(functionName, loopParameters, currentState, isAsyncBlockContainingAwait); - let loop: IterationStatement; + let loop: Statement; if (convert) { - loop = convert(node, convertedLoopBodyStatements); + loop = convert(node, outermostLabeledStatement, convertedLoopBodyStatements); } else { - loop = getMutableClone(node); + let clone = getMutableClone(node); // clean statement part - loop.statement = undefined; + clone.statement = undefined; // visit childnodes to transform initializer/condition/incrementor parts - loop = visitEachChild(loop, visitor, context); + clone = visitEachChild(clone, visitor, context); // set loop statement - loop.statement = createBlock( + clone.statement = createBlock( convertedLoopBodyStatements, /*location*/ undefined, /*multiline*/ true ); // reset and re-aggregate the transform flags - loop.transformFlags = 0; - aggregateTransformFlags(loop); + clone.transformFlags = 0; + aggregateTransformFlags(clone); + loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel); } - - statements.push( - currentParent.kind === SyntaxKind.LabeledStatement - ? createLabel((currentParent).label, loop) - : loop - ); + statements.push(loop); return statements; } @@ -2628,11 +2745,15 @@ namespace ts { case SyntaxKind.SetAccessor: const accessors = getAllAccessorDeclarations(node.properties, property); if (property === accessors.firstAccessor) { - expressions.push(transformAccessorsToExpression(receiver, accessors, node.multiLine)); + expressions.push(transformAccessorsToExpression(receiver, accessors, node, node.multiLine)); } break; + case SyntaxKind.MethodDeclaration: + expressions.push(transformObjectLiteralMethodDeclarationToExpression(property, receiver, node, node.multiLine)); + break; + case SyntaxKind.PropertyAssignment: expressions.push(transformPropertyAssignmentToExpression(property, receiver, node.multiLine)); break; @@ -2641,10 +2762,6 @@ namespace ts { expressions.push(transformShorthandPropertyAssignmentToExpression(property, receiver, node.multiLine)); break; - case SyntaxKind.MethodDeclaration: - expressions.push(transformObjectLiteralMethodDeclarationToExpression(property, receiver, node.multiLine)); - break; - default: Debug.failBadSyntaxKind(node); break; @@ -2703,38 +2820,46 @@ namespace ts { * @param method The MethodDeclaration node. * @param receiver The receiver for the assignment. */ - function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, startsOnNewLine: boolean) { + function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, container: Node, startsOnNewLine: boolean) { + const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None); const expression = createAssignment( createMemberAccessForPropertyName( receiver, visitNode(method.name, visitor, isPropertyName) ), - transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined), + transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined, container), /*location*/ method ); if (startsOnNewLine) { expression.startsOnNewLine = true; } + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return expression; } function visitCatchClause(node: CatchClause): CatchClause { - Debug.assert(isBindingPattern(node.variableDeclaration.name)); - - const temp = createTempVariable(undefined); - const newVariableDeclaration = createVariableDeclaration(temp, undefined, undefined, node.variableDeclaration); - - const vars = flattenDestructuringBinding( - node.variableDeclaration, - visitor, - context, - FlattenLevel.All, - temp - ); - const list = createVariableDeclarationList(vars, /*location*/node.variableDeclaration, /*flags*/node.variableDeclaration.flags); - const destructure = createVariableStatement(undefined, list); + const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); + let updated: CatchClause; + if (isBindingPattern(node.variableDeclaration.name)) { + const temp = createTempVariable(undefined); + const newVariableDeclaration = createVariableDeclaration(temp, undefined, undefined, node.variableDeclaration); + const vars = flattenDestructuringBinding( + node.variableDeclaration, + visitor, + context, + FlattenLevel.All, + temp + ); + const list = createVariableDeclarationList(vars, /*location*/node.variableDeclaration, /*flags*/node.variableDeclaration.flags); + const destructure = createVariableStatement(undefined, list); + updated = updateCatchClause(node, newVariableDeclaration, addStatementToStartOfBlock(node.block, destructure)); + } + else { + updated = visitEachChild(node, visitor, context); + } - return updateCatchClause(node, newVariableDeclaration, addStatementToStartOfBlock(node.block, destructure)); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; } function addStatementToStartOfBlock(block: Block, statement: Statement): Block { @@ -2753,7 +2878,7 @@ namespace ts { // Methods on classes are handled in visitClassDeclaration/visitClassExpression. // Methods with computed property names are handled in visitObjectLiteralExpression. Debug.assert(!isComputedPropertyName(node.name)); - const functionExpression = transformFunctionLikeToExpression(node, /*location*/ moveRangePos(node, -1), /*name*/ undefined); + const functionExpression = transformFunctionLikeToExpression(node, /*location*/ moveRangePos(node, -1), /*name*/ undefined, /*container*/ undefined); setEmitFlags(functionExpression, EmitFlags.NoLeadingComments | getEmitFlags(functionExpression)); return createPropertyAssignment( node.name, @@ -2762,6 +2887,22 @@ namespace ts { ); } + /** + * Visits an AccessorDeclaration of an ObjectLiteralExpression. + * + * @param node An AccessorDeclaration node. + */ + function visitAccessorDeclaration(node: AccessorDeclaration): AccessorDeclaration { + Debug.assert(!isComputedPropertyName(node.name)); + const savedConvertedLoopState = convertedLoopState; + convertedLoopState = undefined; + const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); + const updated = visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; + return updated; + } + /** * Visits a ShorthandPropertyAssignment and transforms it into a PropertyAssignment. * @@ -2775,6 +2916,13 @@ namespace ts { ); } + function visitComputedPropertyName(node: ComputedPropertyName) { + const ancestorFacts = enterSubtree(HierarchyFacts.ComputedPropertyNameExcludes, HierarchyFacts.ComputedPropertyNameIncludes); + const updated = visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; + } + /** * Visits a YieldExpression node. * @@ -2791,8 +2939,11 @@ namespace ts { * @param node An ArrayLiteralExpression node. */ 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); + if (node.transformFlags & TransformFlags.ES2015) { + // We are here because we contain a SpreadElementExpression. + return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, node.multiLine, /*hasTrailingComma*/ node.elements.hasTrailingComma); + } + return visitEachChild(node, visitor, context); } /** @@ -2801,7 +2952,15 @@ namespace ts { * @param node a CallExpression. */ function visitCallExpression(node: CallExpression) { - return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true); + if (node.transformFlags & TransformFlags.ES2015) { + return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true); + } + return updateCall( + node, + visitNode(node.expression, callExpressionVisitor, isExpression), + /*typeArguments*/ undefined, + visitNodes(node.arguments, visitor, isExpression) + ); } function visitImmediateSuperCallInBody(node: CallExpression) { @@ -2833,7 +2992,7 @@ namespace ts { // _super.prototype.m.apply(this, a.concat([b])) resultingCall = createFunctionApply( - visitNode(target, visitor, isExpression), + visitNode(target, callExpressionVisitor, isExpression), visitNode(thisArg, visitor, isExpression), transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) ); @@ -2849,7 +3008,7 @@ namespace ts { // _super.m.call(this, a) // _super.prototype.m.call(this, a) resultingCall = createFunctionCall( - visitNode(target, visitor, isExpression), + visitNode(target, callExpressionVisitor, isExpression), visitNode(thisArg, visitor, isExpression), visitNodes(node.arguments, visitor, isExpression), /*location*/ node @@ -2864,11 +3023,11 @@ namespace ts { resultingCall, actualThis ); - return assignToCapturedThis + resultingCall = assignToCapturedThis ? createAssignment(createIdentifier("_this"), initializer) : initializer; } - return resultingCall; + return setOriginalNode(resultingCall, node); } /** @@ -2877,25 +3036,26 @@ namespace ts { * @param node A NewExpression node. */ function visitNewExpression(node: NewExpression): LeftHandSideExpression { - // We are here because we contain a SpreadElementExpression. - Debug.assert((node.transformFlags & TransformFlags.ContainsSpread) !== 0); - - // [source] - // new C(...a) - // - // [output] - // new ((_a = C).bind.apply(_a, [void 0].concat(a)))() - - const { target, thisArg } = createCallBinding(createPropertyAccess(node.expression, "bind"), hoistVariableDeclaration); - return createNew( - createFunctionApply( - visitNode(target, visitor, isExpression), - thisArg, - transformAndSpreadElements(createNodeArray([createVoidZero(), ...node.arguments]), /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) - ), - /*typeArguments*/ undefined, - [] - ); + if (node.transformFlags & TransformFlags.ContainsSpread) { + // We are here because we contain a SpreadElementExpression. + // [source] + // new C(...a) + // + // [output] + // new ((_a = C).bind.apply(_a, [void 0].concat(a)))() + + const { target, thisArg } = createCallBinding(createPropertyAccess(node.expression, "bind"), hoistVariableDeclaration); + return createNew( + createFunctionApply( + visitNode(target, visitor, isExpression), + thisArg, + transformAndSpreadElements(createNodeArray([createVoidZero(), ...node.arguments]), /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) + ), + /*typeArguments*/ undefined, + [] + ); + } + return visitEachChild(node, visitor, context); } /** @@ -3131,11 +3291,9 @@ namespace ts { /** * Visits the `super` keyword */ - function visitSuperKeyword(): LeftHandSideExpression { - return enclosingNonAsyncFunctionBody - && isClassElement(enclosingNonAsyncFunctionBody) - && !hasModifier(enclosingNonAsyncFunctionBody, ModifierFlags.Static) - && currentParent.kind !== SyntaxKind.CallExpression + function visitSuperKeyword(isExpressionOfCall: boolean): LeftHandSideExpression { + return hierarchyFacts & HierarchyFacts.NonStaticClassElement + && !isExpressionOfCall ? createPropertyAccess(createIdentifier("_super"), "prototype") : createIdentifier("_super"); } @@ -3146,16 +3304,18 @@ namespace ts { * @param node The node to be printed. */ function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { - const savedEnclosingFunction = enclosingFunction; - if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis && isFunctionLike(node)) { // If we are tracking a captured `this`, keep track of the enclosing function. - enclosingFunction = node; + const ancestorFacts = enterSubtree( + HierarchyFacts.FunctionExcludes, + getEmitFlags(node) & EmitFlags.CapturesThis + ? HierarchyFacts.FunctionIncludes | HierarchyFacts.CapturesThis + : HierarchyFacts.FunctionIncludes); + previousOnEmitNode(emitContext, node, emitCallback); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return; } - previousOnEmitNode(emitContext, node, emitCallback); - - enclosingFunction = savedEnclosingFunction; } /** @@ -3283,11 +3443,9 @@ namespace ts { */ function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis - && enclosingFunction - && getEmitFlags(enclosingFunction) & EmitFlags.CapturesThis) { + && hierarchyFacts & HierarchyFacts.CapturesThis) { return createIdentifier("_this", /*location*/ node); } - return node; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 6f491f6708fee..3e10ffdddd4ad 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -900,6 +900,17 @@ namespace ts { return false; } + export function unwrapInnermostStatmentOfLabel(node: LabeledStatement, beforeUnwrapLabelCallback?: (node: LabeledStatement) => void) { + while (true) { + if (beforeUnwrapLabelCallback) { + beforeUnwrapLabelCallback(node); + } + if (node.statement.kind !== SyntaxKind.LabeledStatement) { + return node.statement; + } + node = node.statement; + } + } export function isFunctionBlock(node: Node) { return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent); diff --git a/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.js b/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.js index 7fde44106c65f..4f350c1a3a39a 100644 --- a/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.js +++ b/tests/baselines/reference/captureSuperPropertyAccessInSuperCall01.js @@ -26,7 +26,7 @@ var A = (function () { var B = (function (_super) { __extends(B, _super); function B() { - var _this = _super.call(this, function () { return _super.blah.call(_this); }) || this; + var _this = _super.call(this, function () { return _super.prototype.blah.call(_this); }) || this; return _this; } return B; diff --git a/tests/baselines/reference/superAccess2.js b/tests/baselines/reference/superAccess2.js index da19770584acf..fc3960fba3eab 100644 --- a/tests/baselines/reference/superAccess2.js +++ b/tests/baselines/reference/superAccess2.js @@ -41,9 +41,9 @@ var Q = (function (_super) { __extends(Q, _super); // Super is not allowed in constructor args function Q(z, zz, zzz) { - if (z === void 0) { z = _super.; } - if (zz === void 0) { zz = _super.; } - if (zzz === void 0) { zzz = function () { return _super.; }; } + if (z === void 0) { z = _super.prototype.; } + if (zz === void 0) { zz = _super.prototype.; } + if (zzz === void 0) { zzz = function () { return _super.prototype.; }; } var _this = _super.call(this) || this; _this.z = z; _this.xx = _super.prototype.; diff --git a/tests/baselines/reference/superInConstructorParam1.js b/tests/baselines/reference/superInConstructorParam1.js index 8c99b64991733..d345bbc8d3a91 100644 --- a/tests/baselines/reference/superInConstructorParam1.js +++ b/tests/baselines/reference/superInConstructorParam1.js @@ -27,7 +27,7 @@ var B = (function () { var C = (function (_super) { __extends(C, _super); function C(a) { - if (a === void 0) { a = _super.foo.call(_this); } + if (a === void 0) { a = _super.prototype.foo.call(_this); } var _this; return _this; } diff --git a/tests/baselines/reference/superInObjectLiterals_ES5.js b/tests/baselines/reference/superInObjectLiterals_ES5.js index 816277e4e9eb6..5bc2b64fe36ea 100644 --- a/tests/baselines/reference/superInObjectLiterals_ES5.js +++ b/tests/baselines/reference/superInObjectLiterals_ES5.js @@ -72,14 +72,14 @@ var obj = { } }, method: function () { - _super.prototype.method.call(this); + _super.method.call(this); }, get prop() { - _super.prototype.method.call(this); + _super.method.call(this); return 10; }, set prop(value) { - _super.prototype.method.call(this); + _super.method.call(this); }, p1: function () { _super.method.call(this); @@ -110,14 +110,14 @@ var B = (function (_super) { } }, method: function () { - _super.prototype.method.call(this); + _super.method.call(this); }, get prop() { - _super.prototype.method.call(this); + _super.method.call(this); return 10; }, set prop(value) { - _super.prototype.method.call(this); + _super.method.call(this); }, p1: function () { _super.method.call(this); diff --git a/tests/baselines/reference/superPropertyAccessInSuperCall01.js b/tests/baselines/reference/superPropertyAccessInSuperCall01.js index f0dc925682c24..de96b2bbc5e0b 100644 --- a/tests/baselines/reference/superPropertyAccessInSuperCall01.js +++ b/tests/baselines/reference/superPropertyAccessInSuperCall01.js @@ -26,7 +26,7 @@ var A = (function () { var B = (function (_super) { __extends(B, _super); function B() { - var _this = _super.call(this, _super.blah.call(_this)) || this; + var _this = _super.call(this, _super.prototype.blah.call(_this)) || this; return _this; } return B; diff --git a/tests/baselines/reference/superPropertyInConstructorBeforeSuperCall.js b/tests/baselines/reference/superPropertyInConstructorBeforeSuperCall.js index 3fcf7167ee527..2387e483c6677 100644 --- a/tests/baselines/reference/superPropertyInConstructorBeforeSuperCall.js +++ b/tests/baselines/reference/superPropertyInConstructorBeforeSuperCall.js @@ -40,7 +40,7 @@ var C1 = (function (_super) { var C2 = (function (_super) { __extends(C2, _super); function C2() { - var _this = _super.call(this, _super.x.call(_this)) || this; + var _this = _super.call(this, _super.prototype.x.call(_this)) || this; return _this; } return C2; diff --git a/tests/baselines/reference/super_inside-object-literal-getters-and-setters.js b/tests/baselines/reference/super_inside-object-literal-getters-and-setters.js index 6815b71ce117d..d6f8e36c526de 100644 --- a/tests/baselines/reference/super_inside-object-literal-getters-and-setters.js +++ b/tests/baselines/reference/super_inside-object-literal-getters-and-setters.js @@ -38,10 +38,10 @@ var ObjectLiteral; var ThisInObjectLiteral = { _foo: '1', get foo() { - return _super.prototype._foo; + return _super._foo; }, set foo(value) { - _super.prototype._foo = value; + _super._foo = value; }, test: function () { return _super._foo; @@ -62,7 +62,7 @@ var SuperObjectTest = (function (_super) { SuperObjectTest.prototype.testing = function () { var test = { get F() { - return _super.prototype.test.call(this); + return _super.test.call(this); } }; };