From 25c7caaef96f1519fc475588af9c2b7214d5dac7 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 7 Dec 2016 13:46:04 -0800 Subject: [PATCH 1/2] Simplify es2015 visitor, replace function tracking with flags --- src/compiler/factory.ts | 13 + src/compiler/transformers/es2015.ts | 947 +++++++++++------- src/compiler/utilities.ts | 12 + tests/baselines/reference/superAccess2.js | 6 +- .../reference/superInConstructorParam1.js | 2 +- .../reference/superInObjectLiterals_ES5.js | 12 +- ...perPropertyInConstructorBeforeSuperCall.js | 2 +- ...side-object-literal-getters-and-setters.js | 6 +- 8 files changed, 598 insertions(+), 402 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ebcea221f9e..3bd88d78447 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1754,6 +1754,19 @@ namespace ts { // Utilities + export function restoreEnclosingLabels(node: Statement, enclosingLabeledStatements: LabeledStatement[]) { + if (enclosingLabeledStatements) { + for (const labeledStatement of enclosingLabeledStatements) { + node = updateLabel( + labeledStatement, + labeledStatement.label, + node + ); + } + } + return node; + } + export interface CallBinding { target: LeftHandSideExpression; thisArg: Expression; diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index c1398f75da6..094261f7002 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -162,6 +162,67 @@ namespace ts { ReplaceWithReturn, } + type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]) => Statement; + + // Facts we track as we descend the tree + const enum AncestorFacts { + None = 0, + 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' + + // 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, + + // Arrow functions are lexically scoped to their container, but are new block scopes. + ArrowFunctionIncludes = ArrowFunction | TopLevel, + ArrowFunctionExcludes = BlockScopeExcludes & ~TopLevel | ConstructorWithCapturedSuper, + + // 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, + } + export function transformES2015(context: TransformationContext) { const { startLexicalEnvironment, @@ -178,15 +239,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 ancestorFacts: AncestorFacts; /** * Used to track if we are emitting body of the converted loop @@ -210,166 +263,86 @@ namespace ts { currentSourceFile = node; currentText = node.text; - const visited = saveStateAndInvoke(node, visitSourceFile); + const visited = visitSourceFile(node); addEmitHelpers(visited, context.readEmitHelpers()); currentSourceFile = undefined; currentText = undefined; + ancestorFacts = AncestorFacts.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; + function setAncestorFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node?: Node, container?: Node) { + if (node) { + switch (node.kind) { + case SyntaxKind.MethodDeclaration: + case SyntaxKind.GetAccessor: + case SyntaxKind.SetAccessor: + if (container && isClassLike(container) && !hasModifier(node, ModifierFlags.Static)) { + includeFacts |= AncestorFacts.NonStaticClassElement; } - } - } - - // 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: + case SyntaxKind.FunctionExpression: + const emitFlags = getEmitFlags(node); + if (emitFlags & EmitFlags.CapturesThis) { + includeFacts |= AncestorFacts.CapturesThis; + } + if (emitFlags & EmitFlags.AsyncFunctionBody) { + excludeFacts &= ~AncestorFacts.NonStaticClassElement; + includeFacts |= AncestorFacts.AsyncFunctionBody; + } + break; + case SyntaxKind.FunctionDeclaration: + if (getEmitFlags(node) & EmitFlags.CapturesThis) { + includeFacts |= AncestorFacts.CapturesThis; + } + break; + case SyntaxKind.Block: + if (ancestorFacts & AncestorFacts.IterationStatement) { + includeFacts = includeFacts & ~AncestorFacts.Block | AncestorFacts.IterationStatementBlock; + excludeFacts |= AncestorFacts.Block; + } break; - - default: - enclosingVariableStatement = undefined; } } - - currentParent = currentNode; - currentNode = node; - } - - function returnCapturedThis(node: Node): Node { - return setOriginalNode(createReturn(createIdentifier("_this")), node); + ancestorFacts = ancestorFacts & ~excludeFacts | includeFacts; } function isReturnVoidStatementInConstructorWithCapturedSuper(node: Node): boolean { - return isInConstructorWithCapturedSuper && node.kind === SyntaxKind.ReturnStatement && !(node).expression; + return ancestorFacts & AncestorFacts.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 + || (ancestorFacts & AncestorFacts.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 +377,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 +418,9 @@ namespace ts { case SyntaxKind.ShorthandPropertyAssignment: return visitShorthandPropertyAssignment(node); + case SyntaxKind.ComputedPropertyName: + return visitComputedPropertyName(node); + case SyntaxKind.ArrayLiteralExpression: return visitArrayLiteralExpression(node); @@ -468,31 +455,39 @@ 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 savedAncestorFacts = ancestorFacts; + setAncestorFacts(AncestorFacts.SourceFileExcludes, AncestorFacts.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()); + ancestorFacts = savedAncestorFacts; return updateSourceFileNode( node, createNodeArray(statements, node.statements) @@ -500,44 +495,64 @@ namespace ts { } function visitSwitchStatement(node: SwitchStatement): SwitchStatement { - Debug.assert(convertedLoopState !== undefined); + 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 savedAllowedNonLabeledJumps = convertedLoopState.allowedNonLabeledJumps; - // for switch statement allow only non-labeled break - convertedLoopState.allowedNonLabeledJumps |= Jump.Break; + function visitCaseBlock(node: CaseBlock): CaseBlock { + const savedAncestorFacts = ancestorFacts; + setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.BlockScopeIncludes); + const updated = visitEachChild(node, visitor, context); + ancestorFacts = savedAncestorFacts; + return updated; + } - const result = visitEachChild(node, visitor, context); - - 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 (ancestorFacts & AncestorFacts.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 +826,14 @@ namespace ts { * @param extendsClauseElement The expression for the class `extends` clause. */ function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, extendsClauseElement: ExpressionWithTypeArguments): void { + const savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.ConstructorExcludes, AncestorFacts.ConstructorIncludes); + convertedLoopState = undefined; + const constructor = getFirstConstructorWithBody(node); const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, extendsClauseElement !== undefined); - const constructorFunction = createFunctionDeclaration( /*decorators*/ undefined, @@ -830,7 +850,11 @@ namespace ts { if (extendsClauseElement) { setEmitFlags(constructorFunction, EmitFlags.CapturesThis); } + statements.push(constructorFunction); + + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; } /** @@ -890,11 +914,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) { + ancestorFacts |= AncestorFacts.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 +1040,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); } } @@ -1369,14 +1389,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; @@ -1407,11 +1427,11 @@ 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 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); @@ -1436,9 +1456,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) ); @@ -1455,7 +1475,7 @@ 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 { // 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); @@ -1468,7 +1488,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); @@ -1477,7 +1497,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); @@ -1514,6 +1534,13 @@ namespace ts { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { enableSubstitutionsForCapturedThis(); } + + const savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.ArrowFunctionExcludes, AncestorFacts.ArrowFunctionIncludes); + convertedLoopState = undefined; + const func = createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, @@ -1524,8 +1551,11 @@ namespace ts { transformFunctionBody(node), node ); + setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; return func; } @@ -1535,7 +1565,13 @@ namespace ts { * @param node a FunctionExpression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - return updateFunctionExpression( + const savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + convertedLoopState = undefined; + + const updated = updateFunctionExpression( node, /*modifiers*/ undefined, node.name, @@ -1544,8 +1580,12 @@ namespace ts { /*type*/ undefined, node.transformFlags & TransformFlags.ES2015 ? transformFunctionBody(node) - : visitFunctionBody(node.body, visitor, context) + : visitFunctionBody(node.body, functionBodyVisitor, context) ); + + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; + return updated; } /** @@ -1554,18 +1594,28 @@ namespace ts { * @param node a FunctionDeclaration node. */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - return updateFunctionDeclaration( + const savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + convertedLoopState = undefined; + + 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) ); + + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; + return updated; } /** @@ -1575,11 +1625,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 savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node, container); + convertedLoopState = undefined; const expression = setOriginalNode( createFunctionExpression( @@ -1589,13 +1640,14 @@ namespace ts { /*typeParameters*/ undefined, visitParameterList(node.parameters, visitor, context), /*type*/ undefined, - saveStateAndInvoke(node, transformFunctionBody), + transformFunctionBody(node), location ), /*original*/ node ); - enclosingNonArrowFunction = savedContainingNonArrowFunction; + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; return expression; } @@ -1689,6 +1741,19 @@ namespace ts { return block; } + function visitBlock(node: Block, isFunctionBody: boolean): Block { + if (!isFunctionBody) { + const savedAncestorFacts = ancestorFacts; + setAncestorFacts(AncestorFacts.BlockExcludes, AncestorFacts.BlockIncludes, node); + const updated = visitEachChild(node, visitor, context); + ancestorFacts = savedAncestorFacts; + return updated; + } + + // A function body is not a block scope. + return visitEachChild(node, visitor, context); + } + /** * Visits an ExpressionStatement that contains a destructuring assignment. * @@ -1742,9 +1807,16 @@ namespace ts { FlattenLevel.All, needsDestructuringValue); } + return visitEachChild(node, visitor, context); } function visitVariableStatement(node: VariableStatement): Statement { + const savedAncestorFacts = ancestorFacts; + if (hasModifier(node, ModifierFlags.Export)) { + ancestorFacts |= AncestorFacts.ExportedVariableStatement; + } + + let updated: Statement; if (convertedLoopState && (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) == 0) { // we are inside a converted loop - hoist variable declarations let assignments: Expression[]; @@ -1767,15 +1839,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); + ancestorFacts = savedAncestorFacts; + return updated; } /** @@ -1784,29 +1860,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 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)); + } + + return declarationList; } - - const declarations = flatten(map(node.declarations, node.flags & NodeFlags.Let - ? visitVariableDeclarationInLetDeclarationList - : visitVariableDeclaration)); - - 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)); - } - - return declarationList; + return visitEachChild(node, visitor, context); } /** @@ -1860,20 +1939,18 @@ namespace ts { const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; const emittedAsTopLevel = - isBlockScopedContainerTopLevel(enclosingBlockScopeContainer) + (ancestorFacts & AncestorFacts.TopLevel) !== 0 || (isCapturedInFunction && isDeclaredInLoop - && isBlock(enclosingBlockScopeContainer) - && isIterationStatement(enclosingBlockScopeContainerParent, /*lookInLabeledStatements*/ false)); + && (ancestorFacts & AncestorFacts.IterationStatementBlock) !== 0); const emitExplicitInitializer = !emittedAsTopLevel - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForInStatement - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForOfStatement + && (ancestorFacts & AncestorFacts.ForInOrForOfStatement) === 0 && (!resolver.isDeclarationWithCollidingName(node) || (isDeclaredInLoop && !isCapturedInFunction - && !isIterationStatement(enclosingBlockScopeContainer, /*lookInLabeledStatements*/ false))); + && (ancestorFacts & (AncestorFacts.ForStatement | AncestorFacts.ForInOrForOfStatement)) === 0)); return emitExplicitInitializer; } @@ -1907,72 +1984,108 @@ 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 savedAncestorFacts = ancestorFacts; + ancestorFacts &= ~AncestorFacts.ExportedVariableStatement; + + 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 + (savedAncestorFacts & AncestorFacts.ExportedVariableStatement) !== 0 ); } + else { + updated = visitEachChild(node, visitor, context); + } - return visitEachChild(node, visitor, context); + ancestorFacts = savedAncestorFacts; + return updated; } function visitLabeledStatement(node: LabeledStatement): VisitResult { - if (convertedLoopState) { - if (!convertedLoopState.labels) { - convertedLoopState.labels = createMap(); + const statement = unwrapInnermostStatmentOfLabel(node); + return isIterationStatement(statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(statement) + ? visitIterationStatement(statement, /*outermostLabeledStatement*/ node) + : restoreEnclosingLabel(visitNode(statement, visitor, isStatement), node); + } + + function unwrapInnermostStatmentOfLabel(node: LabeledStatement) { + if (convertedLoopState && !convertedLoopState.labels) { + convertedLoopState.labels = createMap(); + } + while (true) { + if (convertedLoopState) { + convertedLoopState.labels[node.label.text] = node.label.text; } - convertedLoopState.labels[node.label.text] = node.label.text; + if (node.statement.kind !== SyntaxKind.LabeledStatement) { + return node.statement; + } + node = node.statement; } + } - let result: VisitResult; - if (isIterationStatement(node.statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node.statement)) { - result = visitNodes(createNodeArray([node.statement]), visitor, isStatement); + function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement): Statement { + if (!outermostLabeledStatement) { + return node; } - else { - result = visitEachChild(node, visitor, context); - } - if (convertedLoopState) { - convertedLoopState.labels[node.label.text] = undefined; + convertedLoopState.labels[outermostLabeledStatement.label.text] = undefined; } - - return result; + return updateLabel( + outermostLabeledStatement, + outermostLabeledStatement.label, + outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement + ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) + : node + ); } - function visitDoStatement(node: DoStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitIterationStatementWithFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { + const savedAncestorFacts = ancestorFacts; + setAncestorFacts(excludeFacts, includeFacts, node); + const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert); + ancestorFacts = savedAncestorFacts; + return updated; } - function visitWhileStatement(node: WhileStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitDoOrWhileStatement(node: DoStatement | WhileStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + AncestorFacts.DoOrWhileStatementExcludes, + AncestorFacts.DoOrWhileStatementIncludes, + node, + outermostLabeledStatement); } - function visitForStatement(node: ForStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitForStatement(node: ForStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + AncestorFacts.ForStatementExcludes, + AncestorFacts.ForStatementIncludes, + node, + outermostLabeledStatement); } - function visitForInStatement(node: ForInStatement) { - return convertIterationStatementBodyIfNecessary(node); + function visitForInStatement(node: ForInStatement, outermostLabeledStatement: LabeledStatement) { + return visitIterationStatementWithFacts( + AncestorFacts.ForInOrForOfStatementExcludes, + AncestorFacts.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( + AncestorFacts.ForInOrForOfStatementExcludes, + AncestorFacts.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) { } @@ -2139,7 +2252,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); + } + + 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); + } } /** @@ -2155,45 +2282,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 && ancestorFacts & AncestorFacts.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; + // 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); + + // 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); } - expressions.push(assignment); - - 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); + return visitEachChild(node, visitor, context); } function shouldConvertIterationStatementBody(node: IterationStatement): boolean { @@ -2224,7 +2362,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) { @@ -2234,7 +2372,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); if (convertedLoopState) { convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps; @@ -2302,8 +2442,7 @@ namespace ts { } const isAsyncBlockContainingAwait = - enclosingNonArrowFunction - && (getEmitFlags(enclosingNonArrowFunction) & EmitFlags.AsyncFunctionBody) !== 0 + ancestorFacts & AncestorFacts.AsyncFunctionBody && (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0; let loopBodyFlags: EmitFlags = 0; @@ -2422,34 +2561,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); } - - statements.push( - currentParent.kind === SyntaxKind.LabeledStatement - ? createLabel((currentParent).label, loop) - : loop - ); + statements.push(loop); return statements; } @@ -2617,11 +2752,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; @@ -2630,10 +2769,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; @@ -2692,13 +2827,13 @@ 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 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) { @@ -2708,22 +2843,28 @@ namespace ts { } 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); - - return updateCatchClause(node, newVariableDeclaration, addStatementToStartOfBlock(node.block, destructure)); + const savedAncestorFacts = ancestorFacts; + setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.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); + } + ancestorFacts = savedAncestorFacts; + return updated; } function addStatementToStartOfBlock(block: Block, statement: Statement): Block { @@ -2742,7 +2883,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, @@ -2751,6 +2892,25 @@ namespace ts { ); } + /** + * Visits an AccessorDeclaration of an ObjectLiteralExpression. + * + * @param node An AccessorDeclaration node. + */ + function visitAccessorDeclaration(node: AccessorDeclaration): AccessorDeclaration { + const savedAncestorFacts = ancestorFacts; + const savedConvertedLoopState = convertedLoopState; + + setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + convertedLoopState = undefined; + + const updated = visitEachChild(node, visitor, context); + + ancestorFacts = savedAncestorFacts; + convertedLoopState = savedConvertedLoopState; + return updated; + } + /** * Visits a ShorthandPropertyAssignment and transforms it into a PropertyAssignment. * @@ -2764,6 +2924,10 @@ namespace ts { ); } + function visitComputedPropertyName(node: ComputedPropertyName) { + return visitEachChild(node, visitor, context); + } + /** * Visits a YieldExpression node. * @@ -2780,8 +2944,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); } /** @@ -2790,7 +2957,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) { @@ -2822,7 +2997,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) ); @@ -2838,7 +3013,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 @@ -2853,11 +3028,11 @@ namespace ts { resultingCall, actualThis ); - return assignToCapturedThis + resultingCall = assignToCapturedThis ? createAssignment(createIdentifier("_this"), initializer) : initializer; } - return resultingCall; + return setOriginalNode(resultingCall, node); } /** @@ -2866,25 +3041,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); + 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)))() - // [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, - [] - ); + 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); } /** @@ -3120,11 +3296,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 ancestorFacts & AncestorFacts.NonStaticClassElement + && !isExpressionOfCall ? createPropertyAccess(createIdentifier("_super"), "prototype") : createIdentifier("_super"); } @@ -3135,16 +3309,15 @@ 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 savedAncestorFacts = ancestorFacts; + setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + previousOnEmitNode(emitContext, node, emitCallback); + ancestorFacts = savedAncestorFacts; + return; } - previousOnEmitNode(emitContext, node, emitCallback); - - enclosingFunction = savedEnclosingFunction; } /** @@ -3272,11 +3445,9 @@ namespace ts { */ function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis - && enclosingFunction - && getEmitFlags(enclosingFunction) & EmitFlags.CapturesThis) { + && ancestorFacts & AncestorFacts.CapturesThis) { return createIdentifier("_this", /*location*/ node); } - return node; } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index ddfa85bc311..328f80abc5c 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -883,6 +883,18 @@ namespace ts { return false; } + export function getAllLabeledStatements(node: LabeledStatement): { statement: Statement; labeledStatements: LabeledStatement[]; } { + switch (node.statement.kind) { + case SyntaxKind.LabeledStatement: + const result = getAllLabeledStatements(node.statement); + if (result) { + result.labeledStatements.push(node); + } + return result; + default: + return { statement: node.statement, labeledStatements: [node] }; + } + } export function isFunctionBlock(node: Node) { return node && node.kind === SyntaxKind.Block && isFunctionLike(node.parent); diff --git a/tests/baselines/reference/superAccess2.js b/tests/baselines/reference/superAccess2.js index da19770584a..fc3960fba3e 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 8c99b649917..d345bbc8d3a 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 816277e4e9e..5bc2b64fe36 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/superPropertyInConstructorBeforeSuperCall.js b/tests/baselines/reference/superPropertyInConstructorBeforeSuperCall.js index 15bc0c40f37..a41400809d2 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() { - return _super.call(this, _super.x.call(_this)) || this; + return _super.call(this, _super.prototype.x.call(_this)) || this; } return C2; }(B)); 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 6815b71ce11..d6f8e36c526 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); } }; }; From fa53eb150e73dd666edf13137baac5031199bc9a Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Mon, 19 Dec 2016 17:03:59 -0800 Subject: [PATCH 2/2] Improve hierarchy fact tracking --- src/compiler/factory.ts | 24 ++- src/compiler/transformers/es2015.ts | 323 +++++++++++++--------------- src/compiler/utilities.ts | 19 +- 3 files changed, 178 insertions(+), 188 deletions(-) diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 3bd88d78447..761253439fa 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -1754,17 +1754,21 @@ namespace ts { // Utilities - export function restoreEnclosingLabels(node: Statement, enclosingLabeledStatements: LabeledStatement[]) { - if (enclosingLabeledStatements) { - for (const labeledStatement of enclosingLabeledStatements) { - node = updateLabel( - labeledStatement, - labeledStatement.label, - node - ); - } + export function restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement, afterRestoreLabelCallback?: (node: LabeledStatement) => void): Statement { + if (!outermostLabeledStatement) { + return node; } - 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 { diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 5f91063beb8..774c6db2cc4 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -164,9 +164,14 @@ namespace ts { type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement, convertedLoopBodyStatements: Statement[]) => Statement; - // Facts we track as we descend the tree - const enum AncestorFacts { + // 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 @@ -180,6 +185,14 @@ namespace ts { 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. @@ -192,11 +205,14 @@ namespace ts { // Functions, methods, and accessors are both new lexical scopes and new block scopes. FunctionIncludes = Function | TopLevel, - FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper, + 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, + 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. @@ -221,6 +237,25 @@ namespace ts { // 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) { @@ -239,7 +274,7 @@ namespace ts { let currentSourceFile: SourceFile; let currentText: string; - let ancestorFacts: AncestorFacts; + let hierarchyFacts: HierarchyFacts; /** * Used to track if we are emitting body of the converted loop @@ -268,49 +303,34 @@ namespace ts { currentSourceFile = undefined; currentText = undefined; - ancestorFacts = AncestorFacts.None; + hierarchyFacts = HierarchyFacts.None; return visited; } - function setAncestorFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node?: Node, container?: Node) { - if (node) { - switch (node.kind) { - case SyntaxKind.MethodDeclaration: - case SyntaxKind.GetAccessor: - case SyntaxKind.SetAccessor: - if (container && isClassLike(container) && !hasModifier(node, ModifierFlags.Static)) { - includeFacts |= AncestorFacts.NonStaticClassElement; - } - break; + /** + * 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; + } - case SyntaxKind.FunctionExpression: - const emitFlags = getEmitFlags(node); - if (emitFlags & EmitFlags.CapturesThis) { - includeFacts |= AncestorFacts.CapturesThis; - } - if (emitFlags & EmitFlags.AsyncFunctionBody) { - excludeFacts &= ~AncestorFacts.NonStaticClassElement; - includeFacts |= AncestorFacts.AsyncFunctionBody; - } - break; - case SyntaxKind.FunctionDeclaration: - if (getEmitFlags(node) & EmitFlags.CapturesThis) { - includeFacts |= AncestorFacts.CapturesThis; - } - break; - case SyntaxKind.Block: - if (ancestorFacts & AncestorFacts.IterationStatement) { - includeFacts = includeFacts & ~AncestorFacts.Block | AncestorFacts.IterationStatementBlock; - excludeFacts |= AncestorFacts.Block; - } - break; - } - } - ancestorFacts = ancestorFacts & ~excludeFacts | includeFacts; + /** + * 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 ancestorFacts & AncestorFacts.ConstructorWithCapturedSuper + return hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && node.kind === SyntaxKind.ReturnStatement && !(node).expression; } @@ -318,7 +338,7 @@ namespace ts { function shouldVisitNode(node: Node): boolean { return (node.transformFlags & TransformFlags.ContainsES2015) !== 0 || convertedLoopState !== undefined - || (ancestorFacts & AncestorFacts.ConstructorWithCapturedSuper && isStatement(node)) + || (hierarchyFacts & HierarchyFacts.ConstructorWithCapturedSuper && isStatement(node)) || (isIterationStatement(node, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(node)); } @@ -479,15 +499,14 @@ namespace ts { } function visitSourceFile(node: SourceFile): SourceFile { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.SourceFileExcludes, AncestorFacts.SourceFileIncludes); + 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()); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updateSourceFileNode( node, createNodeArray(statements, node.statements) @@ -507,10 +526,9 @@ namespace ts { } function visitCaseBlock(node: CaseBlock): CaseBlock { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.BlockScopeIncludes); + const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); const updated = visitEachChild(node, visitor, context); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } @@ -545,7 +563,7 @@ namespace ts { function visitThisKeyword(node: Node): Node { if (convertedLoopState) { - if (ancestorFacts & AncestorFacts.ArrowFunction) { + if (hierarchyFacts & HierarchyFacts.ArrowFunction) { // if the enclosing function is an ArrowFunction is then we use the captured 'this' keyword. convertedLoopState.containsLexicalThis = true; return node; @@ -826,12 +844,9 @@ namespace ts { * @param extendsClauseElement The expression for the class `extends` clause. */ function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, extendsClauseElement: ExpressionWithTypeArguments): void { - const savedAncestorFacts = ancestorFacts; const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.ConstructorExcludes, AncestorFacts.ConstructorIncludes); convertedLoopState = undefined; - + const ancestorFacts = enterSubtree(HierarchyFacts.ConstructorExcludes, HierarchyFacts.ConstructorIncludes); const constructor = getFirstConstructorWithBody(node); const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, extendsClauseElement !== undefined); const constructorFunction = @@ -852,8 +867,7 @@ namespace ts { } statements.push(constructorFunction); - - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; } @@ -915,7 +929,7 @@ namespace ts { if (constructor) { if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture) { - ancestorFacts |= AncestorFacts.ConstructorWithCapturedSuper; + hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; } addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); @@ -1431,6 +1445,7 @@ namespace ts { * @param member The MethodDeclaration node. */ 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); @@ -1450,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; } @@ -1479,6 +1496,8 @@ namespace ts { * @param receiver The receiver for the member. */ 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); @@ -1525,6 +1544,8 @@ namespace ts { if (startsOnNewLine) { call.startsOnNewLine = true; } + + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return call; } @@ -1537,13 +1558,9 @@ namespace ts { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { enableSubstitutionsForCapturedThis(); } - - const savedAncestorFacts = ancestorFacts; const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.ArrowFunctionExcludes, AncestorFacts.ArrowFunctionIncludes); convertedLoopState = undefined; - + const ancestorFacts = enterSubtree(HierarchyFacts.ArrowFunctionExcludes, HierarchyFacts.ArrowFunctionIncludes); const func = createFunctionExpression( /*modifiers*/ undefined, /*asteriskToken*/ undefined, @@ -1554,10 +1571,9 @@ namespace ts { transformFunctionBody(node), node ); - setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return func; } @@ -1568,12 +1584,11 @@ namespace ts { * @param node a FunctionExpression node. */ function visitFunctionExpression(node: FunctionExpression): Expression { - const savedAncestorFacts = ancestorFacts; + const ancestorFacts = getEmitFlags(node) & EmitFlags.AsyncFunctionBody + ? enterSubtree(HierarchyFacts.AsyncFunctionBodyExcludes, HierarchyFacts.AsyncFunctionBodyIncludes) + : enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); convertedLoopState = undefined; - const updated = updateFunctionExpression( node, /*modifiers*/ undefined, @@ -1585,8 +1600,7 @@ namespace ts { ? transformFunctionBody(node) : visitFunctionBody(node.body, functionBodyVisitor, context) ); - - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updated; } @@ -1597,12 +1611,9 @@ namespace ts { * @param node a FunctionDeclaration node. */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - const savedAncestorFacts = ancestorFacts; const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); convertedLoopState = undefined; - + const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); const updated = updateFunctionDeclaration( node, /*decorators*/ undefined, @@ -1616,7 +1627,7 @@ namespace ts { : visitFunctionBody(node.body, functionBodyVisitor, context) ); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updated; } @@ -1629,12 +1640,11 @@ namespace ts { * @param name The name of the new FunctionExpression. */ function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location: TextRange, name: Identifier, container: Node): FunctionExpression { - const savedAncestorFacts = ancestorFacts; const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node, container); 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, @@ -1648,8 +1658,7 @@ namespace ts { ), /*original*/ node ); - - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return expression; } @@ -1745,16 +1754,16 @@ namespace ts { } function visitBlock(node: Block, isFunctionBody: boolean): Block { - if (!isFunctionBody) { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockExcludes, AncestorFacts.BlockIncludes, node); - const updated = visitEachChild(node, visitor, context); - ancestorFacts = savedAncestorFacts; - return updated; + if (isFunctionBody) { + // A function body is not a block scope. + return visitEachChild(node, visitor, context); } - - // 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; } /** @@ -1814,13 +1823,9 @@ namespace ts { } function visitVariableStatement(node: VariableStatement): Statement { - const savedAncestorFacts = ancestorFacts; - if (hasModifier(node, ModifierFlags.Export)) { - ancestorFacts |= AncestorFacts.ExportedVariableStatement; - } - + const ancestorFacts = enterSubtree(HierarchyFacts.None, hasModifier(node, ModifierFlags.Export) ? HierarchyFacts.ExportedVariableStatement : HierarchyFacts.None); let updated: Statement; - if (convertedLoopState && (getCombinedNodeFlags(node.declarationList) & NodeFlags.BlockScoped) == 0) { + 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) { @@ -1853,7 +1858,7 @@ namespace ts { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } @@ -1942,18 +1947,18 @@ namespace ts { const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; const emittedAsTopLevel = - (ancestorFacts & AncestorFacts.TopLevel) !== 0 + (hierarchyFacts & HierarchyFacts.TopLevel) !== 0 || (isCapturedInFunction && isDeclaredInLoop - && (ancestorFacts & AncestorFacts.IterationStatementBlock) !== 0); + && (hierarchyFacts & HierarchyFacts.IterationStatementBlock) !== 0); const emitExplicitInitializer = !emittedAsTopLevel - && (ancestorFacts & AncestorFacts.ForInOrForOfStatement) === 0 + && (hierarchyFacts & HierarchyFacts.ForInOrForOfStatement) === 0 && (!resolver.isDeclarationWithCollidingName(node) || (isDeclaredInLoop && !isCapturedInFunction - && (ancestorFacts & (AncestorFacts.ForStatement | AncestorFacts.ForInOrForOfStatement)) === 0)); + && (hierarchyFacts & (HierarchyFacts.ForStatement | HierarchyFacts.ForInOrForOfStatement)) === 0)); return emitExplicitInitializer; } @@ -1987,9 +1992,7 @@ namespace ts { * @param node A VariableDeclaration node. */ function visitVariableDeclaration(node: VariableDeclaration): VisitResult { - const savedAncestorFacts = ancestorFacts; - ancestorFacts &= ~AncestorFacts.ExportedVariableStatement; - + const ancestorFacts = enterSubtree(HierarchyFacts.ExportedVariableStatement, HierarchyFacts.None); let updated: VisitResult; if (isBindingPattern(node.name)) { updated = flattenDestructuringBinding( @@ -1998,91 +2001,70 @@ namespace ts { context, FlattenLevel.All, /*value*/ undefined, - (savedAncestorFacts & AncestorFacts.ExportedVariableStatement) !== 0 + (ancestorFacts & HierarchyFacts.ExportedVariableStatement) !== 0 ); } else { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } - function visitLabeledStatement(node: LabeledStatement): VisitResult { - const statement = unwrapInnermostStatmentOfLabel(node); - return isIterationStatement(statement, /*lookInLabeledStatements*/ false) && shouldConvertIterationStatementBody(statement) - ? visitIterationStatement(statement, /*outermostLabeledStatement*/ node) - : restoreEnclosingLabel(visitNode(statement, visitor, isStatement), node); + function recordLabel(node: LabeledStatement) { + convertedLoopState.labels[node.label.text] = node.label.text; } - function unwrapInnermostStatmentOfLabel(node: LabeledStatement) { + function resetLabel(node: LabeledStatement) { + convertedLoopState.labels[node.label.text] = undefined; + } + + function visitLabeledStatement(node: LabeledStatement): VisitResult { if (convertedLoopState && !convertedLoopState.labels) { convertedLoopState.labels = createMap(); } - while (true) { - if (convertedLoopState) { - convertedLoopState.labels[node.label.text] = node.label.text; - } - if (node.statement.kind !== SyntaxKind.LabeledStatement) { - return node.statement; - } - node = node.statement; - } + 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 restoreEnclosingLabel(node: Statement, outermostLabeledStatement: LabeledStatement): Statement { - if (!outermostLabeledStatement) { - return node; - } - if (convertedLoopState) { - convertedLoopState.labels[outermostLabeledStatement.label.text] = undefined; - } - return updateLabel( - outermostLabeledStatement, - outermostLabeledStatement.label, - outermostLabeledStatement.statement.kind === SyntaxKind.LabeledStatement - ? restoreEnclosingLabel(node, outermostLabeledStatement.statement) - : node - ); - } - - function visitIterationStatementWithFacts(excludeFacts: AncestorFacts, includeFacts: AncestorFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(excludeFacts, includeFacts, node); + function visitIterationStatementWithFacts(excludeFacts: HierarchyFacts, includeFacts: HierarchyFacts, node: IterationStatement, outermostLabeledStatement: LabeledStatement, convert?: LoopConverter) { + const ancestorFacts = enterSubtree(excludeFacts, includeFacts); const updated = convertIterationStatementBodyIfNecessary(node, outermostLabeledStatement, convert); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } function visitDoOrWhileStatement(node: DoStatement | WhileStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.DoOrWhileStatementExcludes, - AncestorFacts.DoOrWhileStatementIncludes, + HierarchyFacts.DoOrWhileStatementExcludes, + HierarchyFacts.DoOrWhileStatementIncludes, node, outermostLabeledStatement); } function visitForStatement(node: ForStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.ForStatementExcludes, - AncestorFacts.ForStatementIncludes, + HierarchyFacts.ForStatementExcludes, + HierarchyFacts.ForStatementIncludes, node, outermostLabeledStatement); } function visitForInStatement(node: ForInStatement, outermostLabeledStatement: LabeledStatement) { return visitIterationStatementWithFacts( - AncestorFacts.ForInOrForOfStatementExcludes, - AncestorFacts.ForInOrForOfStatementIncludes, + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, node, outermostLabeledStatement); } function visitForOfStatement(node: ForOfStatement, outermostLabeledStatement: LabeledStatement): VisitResult { return visitIterationStatementWithFacts( - AncestorFacts.ForInOrForOfStatementExcludes, - AncestorFacts.ForInOrForOfStatementIncludes, + HierarchyFacts.ForInOrForOfStatementExcludes, + HierarchyFacts.ForInOrForOfStatementIncludes, node, outermostLabeledStatement, convertForOfToFor); @@ -2255,7 +2237,7 @@ namespace ts { // Disable trailing source maps for the OpenParenToken to align source map emit with the old emitter. setEmitFlags(forStatement, EmitFlags.NoTokenTrailingSourceMaps); - return restoreEnclosingLabel(forStatement, outermostLabeledStatement); + return restoreEnclosingLabel(forStatement, outermostLabeledStatement, convertedLoopState && resetLabel); } function visitIterationStatement(node: IterationStatement, outermostLabeledStatement: LabeledStatement) { @@ -2288,7 +2270,7 @@ namespace ts { let numInitialPropertiesWithoutYield = numProperties; for (let i = 0; i < numProperties; i++) { const property = properties[i]; - if ((property.transformFlags & TransformFlags.ContainsYield && ancestorFacts & AncestorFacts.AsyncFunctionBody) + if ((property.transformFlags & TransformFlags.ContainsYield && hierarchyFacts & HierarchyFacts.AsyncFunctionBody) && i < numInitialPropertiesWithoutYield) { numInitialPropertiesWithoutYield = i; } @@ -2377,7 +2359,7 @@ namespace ts { const result = convert ? convert(node, outermostLabeledStatement, /*convertedLoopBodyStatements*/ undefined) - : restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement); + : restoreEnclosingLabel(visitEachChild(node, visitor, context), outermostLabeledStatement, convertedLoopState && resetLabel); if (convertedLoopState) { convertedLoopState.allowedNonLabeledJumps = saveAllowedNonLabeledJumps; @@ -2453,7 +2435,7 @@ namespace ts { } const isAsyncBlockContainingAwait = - ancestorFacts & AncestorFacts.AsyncFunctionBody + hierarchyFacts & HierarchyFacts.AsyncFunctionBody && (node.statement.transformFlags & TransformFlags.ContainsYield) !== 0; let loopBodyFlags: EmitFlags = 0; @@ -2592,7 +2574,7 @@ namespace ts { // reset and re-aggregate the transform flags clone.transformFlags = 0; aggregateTransformFlags(clone); - loop = restoreEnclosingLabel(clone, outermostLabeledStatement); + loop = restoreEnclosingLabel(clone, outermostLabeledStatement, convertedLoopState && resetLabel); } statements.push(loop); @@ -2839,6 +2821,7 @@ namespace ts { * @param receiver The receiver for the assignment. */ function transformObjectLiteralMethodDeclarationToExpression(method: MethodDeclaration, receiver: Expression, container: Node, startsOnNewLine: boolean) { + const ancestorFacts = enterSubtree(HierarchyFacts.None, HierarchyFacts.None); const expression = createAssignment( createMemberAccessForPropertyName( receiver, @@ -2850,12 +2833,12 @@ namespace ts { if (startsOnNewLine) { expression.startsOnNewLine = true; } + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return expression; } function visitCatchClause(node: CatchClause): CatchClause { - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.BlockScopeExcludes, AncestorFacts.BlockScopeIncludes); + const ancestorFacts = enterSubtree(HierarchyFacts.BlockScopeExcludes, HierarchyFacts.BlockScopeIncludes); let updated: CatchClause; if (isBindingPattern(node.variableDeclaration.name)) { const temp = createTempVariable(undefined); @@ -2874,7 +2857,8 @@ namespace ts { else { updated = visitEachChild(node, visitor, context); } - ancestorFacts = savedAncestorFacts; + + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updated; } @@ -2909,15 +2893,12 @@ namespace ts { * @param node An AccessorDeclaration node. */ function visitAccessorDeclaration(node: AccessorDeclaration): AccessorDeclaration { - const savedAncestorFacts = ancestorFacts; + Debug.assert(!isComputedPropertyName(node.name)); const savedConvertedLoopState = convertedLoopState; - - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); convertedLoopState = undefined; - + const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); const updated = visitEachChild(node, visitor, context); - - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updated; } @@ -2936,7 +2917,10 @@ namespace ts { } function visitComputedPropertyName(node: ComputedPropertyName) { - return visitEachChild(node, visitor, context); + const ancestorFacts = enterSubtree(HierarchyFacts.ComputedPropertyNameExcludes, HierarchyFacts.ComputedPropertyNameIncludes); + const updated = visitEachChild(node, visitor, context); + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + return updated; } /** @@ -3308,7 +3292,7 @@ namespace ts { * Visits the `super` keyword */ function visitSuperKeyword(isExpressionOfCall: boolean): LeftHandSideExpression { - return ancestorFacts & AncestorFacts.NonStaticClassElement + return hierarchyFacts & HierarchyFacts.NonStaticClassElement && !isExpressionOfCall ? createPropertyAccess(createIdentifier("_super"), "prototype") : createIdentifier("_super"); @@ -3322,10 +3306,13 @@ namespace ts { function onEmitNode(emitContext: EmitContext, node: Node, emitCallback: (emitContext: EmitContext, node: Node) => void) { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis && isFunctionLike(node)) { // If we are tracking a captured `this`, keep track of the enclosing function. - const savedAncestorFacts = ancestorFacts; - setAncestorFacts(AncestorFacts.FunctionExcludes, AncestorFacts.FunctionIncludes, node); + const ancestorFacts = enterSubtree( + HierarchyFacts.FunctionExcludes, + getEmitFlags(node) & EmitFlags.CapturesThis + ? HierarchyFacts.FunctionIncludes | HierarchyFacts.CapturesThis + : HierarchyFacts.FunctionIncludes); previousOnEmitNode(emitContext, node, emitCallback); - ancestorFacts = savedAncestorFacts; + exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return; } previousOnEmitNode(emitContext, node, emitCallback); @@ -3456,7 +3443,7 @@ namespace ts { */ function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { if (enabledSubstitutions & ES2015SubstitutionFlags.CapturedThis - && ancestorFacts & AncestorFacts.CapturesThis) { + && hierarchyFacts & HierarchyFacts.CapturesThis) { return createIdentifier("_this", /*location*/ node); } return node; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index d068a303cb4..3e10ffdddd4 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -900,16 +900,15 @@ namespace ts { return false; } - export function getAllLabeledStatements(node: LabeledStatement): { statement: Statement; labeledStatements: LabeledStatement[]; } { - switch (node.statement.kind) { - case SyntaxKind.LabeledStatement: - const result = getAllLabeledStatements(node.statement); - if (result) { - result.labeledStatements.push(node); - } - return result; - default: - return { statement: node.statement, labeledStatements: [node] }; + 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; } }