diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 4ea2489de8..f1b6e0053b 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -1930,7 +1930,8 @@ namespace ts { case SyntaxKind.CallExpression: excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; if (subtreeFlags & TransformFlags.ContainsSpreadElementExpression - || isSuperCall(node)) { + || isSuperCall(node) + || isSuperPropertyCall(node)) { // If the this node contains a SpreadElementExpression, or is a super call, then it is an ES6 // node. transformFlags |= TransformFlags.AssertES6; @@ -1969,12 +1970,18 @@ namespace ts { } } + // If the expression of a ParenthesizedExpression is a destructuring assignment, + // then the ParenthesizedExpression is a destructuring assignment. + if ((node).expression.transformFlags & TransformFlags.DestructuringAssignment) { + transformFlags |= TransformFlags.DestructuringAssignment; + } + break; case SyntaxKind.BinaryExpression: if (isDestructuringAssignment(node)) { // Destructuring assignments are ES6 syntax. - transformFlags |= TransformFlags.AssertES6; + transformFlags |= TransformFlags.AssertES6 | TransformFlags.DestructuringAssignment; } else if ((node).operatorToken.kind === SyntaxKind.AsteriskAsteriskToken || (node).operatorToken.kind === SyntaxKind.AsteriskAsteriskEqualsToken) { @@ -1984,6 +1991,16 @@ namespace ts { break; + case SyntaxKind.ExpressionStatement: + // If the expression of an expression statement is a destructuring assignment, + // then we treat the statement as ES6 so that we can indicate that we do not + // need to hold on to the right-hand side. + if ((node).expression.transformFlags & TransformFlags.DestructuringAssignment) { + transformFlags |= TransformFlags.AssertES6; + } + + break; + case SyntaxKind.Parameter: // If the parameter has a question token, then it is TypeScript syntax. if ((node).questionToken) { diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index b7a1bd0b05..cf90007062 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -7495,7 +7495,7 @@ namespace ts { // This is required for destructuring assignments, as a call expression cannot be used as the target of a destructuring assignment // while a property access can. if (container.kind === SyntaxKind.MethodDeclaration && container.flags & NodeFlags.Async) { - if (isSuperPropertyOrElementAccess(node.parent) && isAssignmentTarget(node.parent)) { + if (isSuperProperty(node.parent) && isAssignmentTarget(node.parent)) { getNodeLinks(container).flags |= NodeCheckFlags.AsyncMethodWithSuperBinding; } else { diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 15a8ce87ea..aeb908c20c 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -220,6 +220,23 @@ namespace ts { return result; } + /** + * Computes the first matching span of elements and returns a tuple of the first span + * and the remaining elements. + */ + export function span(array: T[], f: (x: T, i: number) => boolean): [T[], T[]] { + if (array) { + for (let i = 0; i < array.length; i++) { + if (!f(array[i], i)) { + return [array.slice(0, i), array.slice(i)]; + } + } + return [array.slice(0), []]; + } + + return undefined; + } + /** * Maps contiguous spans of values with the same key. * diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index bda2bd7950..d279c419b5 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2339,7 +2339,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, ge superCall = true; } else { - superCall = isSuperPropertyOrElementAccess(expression); + superCall = isSuperProperty(expression); isAsyncMethodWithSuper = superCall && isInAsyncMethodWithSuperInES6(node); emit(expression); } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index ee886fa334..c4813ab13c 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -152,7 +152,7 @@ namespace ts { /** * Creates a shallow, memberwise clone of a node for mutation. */ - export function getMutableNode(node: T): T { + export function getMutableClone(node: T): T { return cloneNode(node, /*location*/ node, node.flags, /*parent*/ undefined, /*original*/ node); } @@ -320,15 +320,21 @@ namespace ts { // Expression - export function createArrayLiteral(elements?: Expression[]) { - const node = createNode(SyntaxKind.ArrayLiteralExpression); + export function createArrayLiteral(elements?: Expression[], location?: TextRange, multiLine?: boolean) { + const node = createNode(SyntaxKind.ArrayLiteralExpression, location); node.elements = parenthesizeListElements(createNodeArray(elements)); + if (multiLine) { + node.multiLine = multiLine; + } return node; } - export function createObjectLiteral(properties?: ObjectLiteralElement[], location?: TextRange) { + export function createObjectLiteral(properties?: ObjectLiteralElement[], location?: TextRange, multiLine?: boolean) { const node = createNode(SyntaxKind.ObjectLiteralExpression, location); node.properties = createNodeArray(properties); + if (multiLine) { + node.multiLine = multiLine; + } return node; } @@ -356,7 +362,7 @@ namespace ts { export function createNew(expression: Expression, argumentsArray: Expression[], location?: TextRange) { const node = createNode(SyntaxKind.NewExpression, location); - node.expression = parenthesizeForAccess(expression); + node.expression = parenthesizeForNew(expression); node.arguments = argumentsArray ? parenthesizeListElements(createNodeArray(argumentsArray)) : undefined; @@ -369,7 +375,7 @@ namespace ts { return node; } - export function createFunctionExpression(asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + export function createFunctionExpression(asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange, original?: Node) { const node = createNode(SyntaxKind.FunctionExpression, location); node.modifiers = undefined; node.asteriskToken = asteriskToken; @@ -378,6 +384,10 @@ namespace ts { node.parameters = createNodeArray(parameters); node.type = undefined; node.body = body; + if (original) { + node.original = original; + } + return node; } @@ -468,9 +478,12 @@ namespace ts { // Element - export function createBlock(statements: Statement[], location?: TextRange): Block { + export function createBlock(statements: Statement[], location?: TextRange, multiLine?: boolean): Block { const block = createNode(SyntaxKind.Block, location); block.statements = createNodeArray(statements); + if (multiLine) { + block.multiLine = true; + } return block; } @@ -573,7 +586,7 @@ namespace ts { return node; } - export function createFunctionDeclaration(modifiers: Modifier[], asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange) { + export function createFunctionDeclaration(modifiers: Modifier[], asteriskToken: Node, name: string | Identifier, parameters: ParameterDeclaration[], body: Block, location?: TextRange, original?: Node) { const node = createNode(SyntaxKind.FunctionDeclaration, location); node.decorators = undefined; setModifiers(node, modifiers); @@ -583,6 +596,9 @@ namespace ts { node.parameters = createNodeArray(parameters); node.type = undefined; node.body = body; + if (original) { + node.original = original; + } return node; } @@ -1068,6 +1084,68 @@ namespace ts { ); } + export interface CallBinding { + target: LeftHandSideExpression; + thisArg: Expression; + } + + export function createCallBinding(expression: Expression, languageVersion?: ScriptTarget): CallBinding { + const callee = skipParentheses(expression); + let thisArg: Expression; + let target: LeftHandSideExpression; + if (isSuperProperty(callee)) { + thisArg = createThis(/*location*/ callee.expression); + target = callee; + } + else if (isSuperCall(callee)) { + thisArg = createThis(/*location*/ callee); + target = languageVersion < ScriptTarget.ES6 ? createIdentifier("_super", /*location*/ callee) : callee; + } + else { + switch (callee.kind) { + case SyntaxKind.PropertyAccessExpression: { + // for `a.b()` target is `(_a = a).b` and thisArg is `_a` + thisArg = createTempVariable(); + target = createPropertyAccess( + createAssignment( + thisArg, + (callee).expression, + /*location*/ (callee).expression + ), + (callee).name, + /*location*/ callee + ); + break; + } + + case SyntaxKind.ElementAccessExpression: { + // for `a[b]()` target is `(_a = a)[b]` and thisArg is `_a` + thisArg = createTempVariable(); + target = createElementAccess( + createAssignment( + thisArg, + (callee).expression, + /*location*/ (callee).expression + ), + (callee).argumentExpression, + /*location*/ callee + ); + + break; + } + + default: { + // for `a()` target is `a` and thisArg is `void 0` + thisArg = createVoidZero(); + target = parenthesizeForAccess(expression); + break; + } + } + } + + return { target, thisArg }; + } + export function inlineExpressions(expressions: Expression[]) { return reduceLeft(expressions, createComma); } @@ -1206,6 +1284,23 @@ namespace ts { || binaryOperator === SyntaxKind.CaretToken; } + /** + * Wraps an expression in parentheses if it is needed in order to use the expression + * as the expression of a NewExpression node. + * + * @param expression The Expression node. + */ + export function parenthesizeForNew(expression: Expression): LeftHandSideExpression { + const lhs = parenthesizeForAccess(expression); + switch (lhs.kind) { + case SyntaxKind.CallExpression: + case SyntaxKind.NewExpression: + return createParen(lhs); + } + + return lhs; + } + /** * Wraps an expression in parentheses if it is needed in order to use the expression for * property or element access. diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 73282e92ec..4db823266c 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -3,14 +3,20 @@ /*@internal*/ namespace ts { + + const enum ES6SubstitutionFlags { + /** Enables substitutions for captured `this` */ + CapturedThis = 1 << 0, + /** Enables substitutions for block-scoped bindings. */ + BlockScopedBindings = 1 << 1, + } + export function transformES6(context: TransformationContext) { const { startLexicalEnvironment, endLexicalEnvironment, hoistVariableDeclaration, setNodeEmitFlags, - enableExpressionSubstitution, - enableEmitNotification, } = context; const resolver = context.getEmitResolver(); @@ -18,24 +24,43 @@ namespace ts { const previousExpressionSubstitution = context.expressionSubstitution; const previousOnBeforeEmitNode = context.onBeforeEmitNode; const previousOnAfterEmitNode = context.onAfterEmitNode; - context.enableExpressionSubstitution(SyntaxKind.Identifier); - context.identifierSubstitution = substituteIdentifier; - context.expressionSubstitution = substituteExpression; context.onBeforeEmitNode = onBeforeEmitNode; context.onAfterEmitNode = onAfterEmitNode; + context.identifierSubstitution = substituteIdentifier; + context.expressionSubstitution = substituteExpression; let currentSourceFile: SourceFile; let currentParent: Node; let currentNode: Node; let enclosingBlockScopeContainer: Node; let enclosingBlockScopeContainerParent: Node; - let containingFunction: FunctionLikeDeclaration; let containingNonArrowFunction: FunctionLikeDeclaration; - let combinedNodeFlags: NodeFlags; - // This stack is is used to support substitutions when printing nodes. - let hasEnabledExpressionSubstitutionForCapturedThis = false; - let containingFunctionStack: FunctionLikeDeclaration[]; + /** + * Keeps track of whether substitutions have been enabled for specific cases. + * They are persisted between each SourceFile transformation and should not + * be reset. + */ + let enabledSubstitutions: ES6SubstitutionFlags; + + /** + * Keeps track of how deeply nested we are within function-likes when printing + * nodes. This is used to determine whether we need to emit `_this` instead of + * `this`. + */ + let containingFunctionDepth: number; + + /** + * The first 31 bits are used to determine whether a containing function is an + * arrow function. + */ + let containingFunctionState: number; + + /** + * If the containingFunctionDepth grows beyond 31 nested function-likes, this + * array is used as a stack to track deeper levels of nesting. + */ + let containingFunctionStack: number[]; return transformSourceFile; @@ -45,24 +70,20 @@ namespace ts { } function visitor(node: Node): Node { - const savedContainingFunction = containingFunction; const savedContainingNonArrowFunction = containingNonArrowFunction; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; const savedEnclosingBlockScopeContainer = enclosingBlockScopeContainer; const savedEnclosingBlockScopeContainerParent = enclosingBlockScopeContainerParent; - const savedCombinedNodeFlags = combinedNodeFlags; onBeforeVisitNode(node); node = visitorWorker(node); - containingFunction = savedContainingFunction; containingNonArrowFunction = savedContainingNonArrowFunction; currentParent = savedCurrentParent; currentNode = savedCurrentNode; enclosingBlockScopeContainer = savedEnclosingBlockScopeContainer; enclosingBlockScopeContainerParent = savedEnclosingBlockScopeContainerParent; - combinedNodeFlags = savedCombinedNodeFlags; return node; } @@ -122,6 +143,9 @@ namespace ts { case SyntaxKind.ForOfStatement: return visitForOfStatement(node); + case SyntaxKind.ExpressionStatement: + return visitExpressionStatement(node); + case SyntaxKind.ObjectLiteralExpression: return visitObjectLiteralExpression(node); @@ -137,8 +161,11 @@ namespace ts { case SyntaxKind.NewExpression: return visitNewExpression(node); + case SyntaxKind.ParenthesizedExpression: + return visitParenthesizedExpression(node, /*needsDestructuringValue*/ true); + case SyntaxKind.BinaryExpression: - return visitBinaryExpression(node); + return visitBinaryExpression(node, /*needsDestructuringValue*/ true); case SyntaxKind.NoSubstitutionTemplateLiteral: case SyntaxKind.TemplateHead: @@ -170,8 +197,6 @@ namespace ts { currentParent = currentNode; currentNode = node; - combinedNodeFlags = combineNodeFlags(currentNode, currentParent, combinedNodeFlags); - if (currentParent) { if (isBlockScope(currentParent, currentGrandparent)) { enclosingBlockScopeContainer = currentParent; @@ -186,17 +211,27 @@ namespace ts { case SyntaxKind.FunctionDeclaration: case SyntaxKind.FunctionExpression: containingNonArrowFunction = currentParent; - containingFunction = currentParent; - break; - - case SyntaxKind.ArrowFunction: - containingFunction = currentParent; break; } } } + /** + * Visits a ClassDeclaration and transforms it into a variable statement. + * + * @parma node A ClassDeclaration node. + */ function visitClassDeclaration(node: ClassDeclaration): Statement { + // [source] + // class C { } + // + // [output] + // var C = (function () { + // function C() { + // } + // return C; + // }()); + return startOnNewLine( createVariableStatement( /*modifiers*/ undefined, @@ -211,11 +246,54 @@ namespace ts { ); } + /** + * Visits a ClassExpression and transforms it into an expression. + * + * @param node A ClassExpression node. + */ function visitClassExpression(node: ClassExpression): Expression { + // [source] + // C = class { } + // + // [output] + // C = (function () { + // function class_1() { + // } + // return class_1; + // }()) + return transformClassLikeDeclarationToExpression(node); } + /** + * Transforms a ClassExpression or ClassDeclaration into an expression. + * + * @param node A ClassExpression or ClassDeclaration node. + */ function transformClassLikeDeclarationToExpression(node: ClassExpression | ClassDeclaration): Expression { + // [source] + // class C extends D { + // constructor() {} + // method() {} + // get prop() {} + // set prop(v) {} + // } + // + // [output] + // (function (_super) { + // __extends(C, _super); + // function C() { + // } + // C.prototype.method = function () {} + // Object.defineProperty(C.prototype, "prop", { + // get: function() {}, + // set: function() {}, + // enumerable: true, + // configurable: true + // }); + // return C; + // }(D)) + const baseTypeNode = getClassExtendsHeritageClauseElement(node); return createParen( createCall( @@ -225,25 +303,40 @@ namespace ts { baseTypeNode ? [createParameter("_super")] : [], transformClassBody(node, baseTypeNode !== undefined) ), - baseTypeNode ? [visitNode(baseTypeNode.expression, visitor, isExpression)] : [] + baseTypeNode + ? [visitNode(baseTypeNode.expression, visitor, isExpression)] + : [] ) ); } + /** + * Transforms a ClassExpression or ClassDeclaration into a function body. + * + * @param node A ClassExpression or ClassDeclaration node. + * @param hasExtendsClause A value indicating whether the class has an `extends` clause. + */ function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): Block { const statements: Statement[] = []; startLexicalEnvironment(); addExtendsHelperIfNeeded(statements, node, hasExtendsClause); addConstructor(statements, node, hasExtendsClause); addClassMembers(statements, node); - addNode(statements, createReturn(getDeclarationName(node))); - addNodes(statements, endLexicalEnvironment()); - return setMultiLine(createBlock(statements), /*multiLine*/ true); + statements.push(createReturn(getDeclarationName(node))); + addRange(statements, endLexicalEnvironment()); + return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); } - function addExtendsHelperIfNeeded(classStatements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { + /** + * Adds a call to the `__extends` helper if needed for a class. + * + * @param statements The statements of the class body function. + * @param node The ClassExpression or ClassDeclaration node. + * @param hasExtendsClause A value indicating whether the class has an `extends` clause. + */ + function addExtendsHelperIfNeeded(statements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { if (hasExtendsClause) { - addNode(classStatements, + statements.push( createStatement( createExtendsHelper(getDeclarationName(node)) ) @@ -251,10 +344,17 @@ namespace ts { } } - function addConstructor(classStatements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { + /** + * Adds the constructor of the class to a class body function. + * + * @param statements The statements of the class body function. + * @param node The ClassExpression or ClassDeclaration node. + * @param hasExtendsClause A value indicating whether the class has an `extends` clause. + */ + function addConstructor(statements: Statement[], node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): void { const constructor = getFirstConstructorWithBody(node); const hasSynthesizedSuper = hasSynthesizedDefaultSuperCall(constructor, hasExtendsClause); - addNode(classStatements, + statements.push( createFunctionDeclaration( /*modifiers*/ undefined, /*asteriskToken*/ undefined, @@ -266,7 +366,19 @@ namespace ts { ); } + /** + * Transforms the parameters of the constructor declaration of a class. + * + * @param constructor The constructor for the class. + * @param hasSynthesizedSuper A value indicating whether the constructor starts with a + * synthesized `super` call. + */ function transformConstructorParameters(constructor: ConstructorDeclaration, hasSynthesizedSuper: boolean): ParameterDeclaration[] { + // If the TypeScript transformer needed to synthesize a constructor for property + // initializers, it would have also added a synthetic `...args` parameter and + // `super` call. + // If this is the case, we do not include the synthetic `...args` parameter and + // will instead use the `arguments` object in ES5/3. if (constructor && !hasSynthesizedSuper) { return visitNodes(constructor.parameters, visitor, isParameter); } @@ -274,28 +386,50 @@ namespace ts { return []; } + /** + * Transforms the body of a constructor declaration of a class. + * + * @param constructor The constructor for the class. + * @param hasExtendsClause A value indicating whether the class has an `extends` clause. + * @param hasSynthesizedSuper A value indicating whether the constructor starts with a + * synthesized `super` call. + */ function transformConstructorBody(constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { const statements: Statement[] = []; startLexicalEnvironment(); if (constructor) { addCaptureThisForNodeIfNeeded(statements, constructor); - addDefaultValueAssignments(statements, constructor); - addRestParameter(statements, constructor, hasSynthesizedSuper); + addDefaultValueAssignmentsIfNeeded(statements, constructor); + addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); } - addDefaultSuperCall(statements, constructor, hasExtendsClause, hasSynthesizedSuper); + addDefaultSuperCallIfNeeded(statements, constructor, hasExtendsClause, hasSynthesizedSuper); if (constructor) { - addNodes(statements, visitNodes(constructor.body.statements, visitor, isStatement, hasSynthesizedSuper ? 1 : 0)); + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, hasSynthesizedSuper ? 1 : 0)); } - addNodes(statements, endLexicalEnvironment()); - return setMultiLine(createBlock(statements, /*location*/ constructor && constructor.body), /*multiLine*/ true); + addRange(statements, endLexicalEnvironment()); + return createBlock(statements, /*location*/ constructor && constructor.body, /*multiLine*/ true); } - function addDefaultSuperCall(statements: Statement[], constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { + /** + * Adds a synthesized call to `_super` if it is needed. + * + * @param statements The statements for the new constructor body. + * @param constructor The constructor for the class. + * @param hasExtendsClause A value indicating whether the class has an `extends` clause. + * @param hasSynthesizedSuper A value indicating whether the constructor starts with a + * synthesized `super` call. + */ + function addDefaultSuperCallIfNeeded(statements: Statement[], constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { + // If the TypeScript transformer needed to synthesize a constructor for property + // initializers, it would have also added a synthetic `...args` parameter and + // `super` call. + // If this is the case, or if the class has an `extends` clause but no + // constructor, we emit a synthesized call to `_super`. if (constructor ? hasSynthesizedSuper : hasExtendsClause) { - addNode(statements, + statements.push( createStatement( createFunctionApply( createIdentifier("_super"), @@ -307,6 +441,11 @@ namespace ts { } } + /** + * Visits a parameter declaration. + * + * @param node A ParameterDeclaration node. + */ function visitParameter(node: ParameterDeclaration): ParameterDeclaration { if (isBindingPattern(node.name)) { // Binding patterns are converted into a generated name and are @@ -334,11 +473,24 @@ namespace ts { } } + /** + * Gets a value indicating whether we need to add default value assignments for a + * function-like node. + * + * @param node A function-like node. + */ function shouldAddDefaultValueAssignments(node: FunctionLikeDeclaration): boolean { return (node.transformFlags & TransformFlags.ContainsDefaultValueAssignments) !== 0; } - function addDefaultValueAssignments(statements: Statement[], node: FunctionLikeDeclaration): void { + /** + * Adds statements to the body of a function-like node if it contains parameters with + * binding patterns or initializers. + * + * @param statements The statements for the new function body. + * @param node A function-like node. + */ + function addDefaultValueAssignmentsIfNeeded(statements: Statement[], node: FunctionLikeDeclaration): void { if (!shouldAddDefaultValueAssignments(node)) { return; } @@ -361,6 +513,14 @@ namespace ts { } } + /** + * Adds statements to the body of a function-like node for parameters with binding patterns + * + * @param statements The statements for the new function body. + * @param parameter The parameter for the function. + * @param name The name of the parameter. + * @param initializer The initializer for the parameter. + */ function addDefaultValueAssignmentForBindingPattern(statements: Statement[], parameter: ParameterDeclaration, name: BindingPattern, initializer: Expression): void { const temp = getGeneratedNameForNode(parameter); @@ -368,17 +528,17 @@ namespace ts { // we usually don't want to emit a var declaration; however, in the presence // of an initializer, we must emit that expression to preserve side effects. if (name.elements.length > 0) { - addNode(statements, + statements.push( createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList( - transformParameterBindingElements(parameter, temp) + flattenParameterDestructuring(parameter, temp, visitor) ) ) ); } else if (initializer) { - addNode(statements, + statements.push( createStatement( createAssignment( temp, @@ -389,12 +549,16 @@ namespace ts { } } - function transformParameterBindingElements(parameter: ParameterDeclaration, name: Identifier) { - return flattenParameterDestructuring(parameter, name, visitor); - } - + /** + * Adds statements to the body of a function-like node for parameters with initializers. + * + * @param statements The statements for the new function body. + * @param parameter The parameter for the function. + * @param name The name of the parameter. + * @param initializer The initializer for the parameter. + */ function addDefaultValueAssignmentForInitializer(statements: Statement[], parameter: ParameterDeclaration, name: Identifier, initializer: Expression): void { - addNode(statements, + statements.push( createIf( createStrictEquality( getSynthesizedClone(name), @@ -415,17 +579,30 @@ namespace ts { ); } - function shouldAddRestParameter(node: ParameterDeclaration) { - return node && node.dotDotDotToken; + /** + * Gets a value indicating whether we need to add statements to handle a rest parameter. + * + * @param node A ParameterDeclaration node. + * @param inConstructorWithSynthesizedSuper A value indicating whether the parameter is + * part of a constructor declaration with a + * synthesized call to `super` + */ + function shouldAddRestParameter(node: ParameterDeclaration, inConstructorWithSynthesizedSuper: boolean) { + return node && node.dotDotDotToken && !inConstructorWithSynthesizedSuper; } - function addRestParameter(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper?: boolean): void { - if (inConstructorWithSynthesizedSuper) { - return; - } - + /** + * Adds statements to the body of a function-like node if it contains a rest parameter. + * + * @param statements The statements for the new function body. + * @param node A function-like node. + * @param inConstructorWithSynthesizedSuper A value indicating whether the parameter is + * part of a constructor declaration with a + * synthesized call to `super` + */ + function addRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): void { const parameter = lastOrUndefined(node.parameters); - if (!shouldAddRestParameter(parameter)) { + if (!shouldAddRestParameter(parameter, inConstructorWithSynthesizedSuper)) { return; } @@ -434,7 +611,7 @@ namespace ts { const temp = createLoopVariable(); // var param = []; - addNode(statements, + statements.push( createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ @@ -449,7 +626,7 @@ namespace ts { // for (var _i = restIndex; _i < arguments.length; _i++) { // param[_i - restIndex] = arguments[_i]; // } - addNode(statements, + statements.push( createFor( createVariableDeclarationList([ createVariableDeclaration(temp, createLiteral(restIndex)) @@ -465,7 +642,7 @@ namespace ts { createAssignment( createElementAccess( name, - restIndex === 0 ? temp : createSubtract(temp, createLiteral(restIndex)) + createSubtract(temp, createLiteral(restIndex)) ), createElementAccess(createIdentifier("arguments"), temp) ) @@ -476,11 +653,16 @@ namespace ts { ); } + /** + * Adds a statement to capture the `this` of a function declaration if it is needed. + * + * @param statements The statements for the new function body. + * @param node A node. + */ function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): void { if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) { - enableExpressionSubstitutionForCapturedThis(); - - addNode(statements, + enableSubstitutionsForCapturedThis(); + statements.push( createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ @@ -494,23 +676,29 @@ namespace ts { } } - function addClassMembers(classStatements: Statement[], node: ClassExpression | ClassDeclaration): void { + /** + * Adds statements to the class body function for a class to define the members of the + * class. + * + * @param statements The statements for the class body function. + * @param node The ClassExpression or ClassDeclaration node. + */ + function addClassMembers(statements: Statement[], node: ClassExpression | ClassDeclaration): void { for (const member of node.members) { switch (member.kind) { case SyntaxKind.SemicolonClassElement: - addNode(classStatements, transformSemicolonClassElementToStatement(member)); + statements.push(transformSemicolonClassElementToStatement(member)); break; case SyntaxKind.MethodDeclaration: - addNode(classStatements, transformClassMethodDeclarationToStatement(node, member)); + statements.push(transformClassMethodDeclarationToStatement(getClassMemberPrefix(node, member), member)); break; case SyntaxKind.GetAccessor: case SyntaxKind.SetAccessor: const accessors = getAllAccessorDeclarations(node.members, member); if (member === accessors.firstAccessor) { - const receiver = getClassMemberPrefix(node, member); - addNode(classStatements, transformAccessorsToStatement(receiver, accessors)); + statements.push(transformAccessorsToStatement(getClassMemberPrefix(node, member), accessors)); } break; @@ -526,42 +714,52 @@ namespace ts { } } + /** + * Transforms a SemicolonClassElement into a statement for a class body function. + * + * @param member The SemicolonClassElement node. + */ function transformSemicolonClassElementToStatement(member: SemicolonClassElement) { - return createEmptyStatement(member); + return createEmptyStatement(/*location*/ member); } - function transformClassMethodDeclarationToStatement(node: ClassExpression | ClassDeclaration, member: MethodDeclaration) { - const savedContainingFunction = containingFunction; - const savedContainingNonArrowFunction = containingNonArrowFunction; - containingFunction = containingNonArrowFunction = member; - const statement = createStatement( + /** + * Transforms a MethodDeclaration into a statement for a class body function. + * + * @param receiver The receiver for the member. + * @param member The MethodDeclaration node. + */ + function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) { + return createStatement( createAssignment( createMemberAccessForPropertyName( - getClassMemberPrefix(node, member), + receiver, visitNode(member.name, visitor, isPropertyName) ), - transformFunctionLikeToExpression(member) + transformFunctionLikeToExpression(member, /*location*/ undefined, /*name*/ undefined) ), /*location*/ member ); - - containingFunction = savedContainingFunction; - containingNonArrowFunction = savedContainingNonArrowFunction; - return statement; } + /** + * Transforms a set of related of get/set accessors into a statement for a class body function. + * + * @param receiver The receiver for the member. + * @param accessors The set of related get/set accessors. + */ function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations): Statement { - const savedContainingFunction = containingFunction; - const savedContainingNonArrowFunction = containingNonArrowFunction; - containingFunction = containingNonArrowFunction = accessors.firstAccessor; - const statement = createStatement( + return createStatement( transformAccessorsToExpression(receiver, accessors) ); - containingFunction = savedContainingFunction; - containingNonArrowFunction = savedContainingNonArrowFunction; - return statement; } + /** + * Transforms a set of related get/set accessors into an expression for either a class + * body function or an ObjectLiteralExpression with computed properties. + * + * @param receiver The receiver for the member. + */ function transformAccessorsToExpression(receiver: LeftHandSideExpression, { firstAccessor, getAccessor, setAccessor }: AllAccessorDeclarations): Expression { return createObjectDefineProperty( receiver, @@ -570,8 +768,8 @@ namespace ts { /*location*/ firstAccessor.name ), { - get: getAccessor && transformFunctionLikeToExpression(getAccessor, /*location*/ getAccessor), - set: setAccessor && transformFunctionLikeToExpression(setAccessor, /*location*/ setAccessor), + get: getAccessor && transformFunctionLikeToExpression(getAccessor, /*location*/ getAccessor, /*name*/ undefined), + set: setAccessor && transformFunctionLikeToExpression(setAccessor, /*location*/ setAccessor, /*name*/ undefined), enumerable: true, configurable: true }, @@ -580,175 +778,289 @@ namespace ts { ); } - function transformFunctionLikeToExpression(node: FunctionLikeDeclaration, location?: TextRange, name?: Identifier): FunctionExpression { - return setOriginalNode( - createFunctionExpression( - /*asteriskToken*/ undefined, - name, - visitNodes(node.parameters, visitor, isParameter), - transformFunctionBody(node), - location - ), - node - ); - } - + /** + * Visits an ArrowFunction and transforms it into a FunctionExpression. + * + * @param node An ArrowFunction node. + */ function visitArrowFunction(node: ArrowFunction) { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { - enableExpressionSubstitutionForCapturedThis(); + enableSubstitutionsForCapturedThis(); } return transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined); } + /** + * Visits a FunctionExpression node. + * + * @param node a FunctionExpression node. + */ function visitFunctionExpression(node: FunctionExpression): Expression { return transformFunctionLikeToExpression(node, /*location*/ node, node.name); } + /** + * Visits a FunctionDeclaration node. + * + * @param node a FunctionDeclaration node. + */ function visitFunctionDeclaration(node: FunctionDeclaration): FunctionDeclaration { - return setOriginalNode( - createFunctionDeclaration( - /*modifiers*/ undefined, - node.asteriskToken, // TODO(rbuckton): downlevel support for generators - node.name, - visitNodes(node.parameters, visitor, isParameter), - transformFunctionBody(node), - /*location*/ node - ), - node + return createFunctionDeclaration( + /*modifiers*/ undefined, + node.asteriskToken, + node.name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + /*location*/ node, + /*original*/ node ); } + /** + * Transforms a function-like node into a FunctionExpression. + * + * @param node The function-like node to transform. + * @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 = containingNonArrowFunction; + if (node.kind !== SyntaxKind.ArrowFunction) { + containingNonArrowFunction = node; + } + + const expression = createFunctionExpression( + /*asteriskToken*/ undefined, + name, + visitNodes(node.parameters, visitor, isParameter), + transformFunctionBody(node), + location, + /*original*/ node + ); + + containingNonArrowFunction = savedContainingNonArrowFunction; + return expression; + } + + /** + * Transforms the body of a function-like node. + * + * @param node A function-like node. + */ function transformFunctionBody(node: FunctionLikeDeclaration) { const statements: Statement[] = []; startLexicalEnvironment(); addCaptureThisForNodeIfNeeded(statements, node); - addDefaultValueAssignments(statements, node); - addRestParameter(statements, node); + addDefaultValueAssignmentsIfNeeded(statements, node); + addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); const body = node.body; if (isBlock(body)) { - addNodes(statements, visitNodes(body.statements, visitor, isStatement)); + addRange(statements, visitNodes(body.statements, visitor, isStatement)); } else { const expression = visitNode(body, visitor, isExpression); if (expression) { - addNode(statements, createReturn(expression, /*location*/ body)); + statements.push(createReturn(expression, /*location*/ body)); } } - addNodes(statements, endLexicalEnvironment()); + addRange(statements, endLexicalEnvironment()); return createBlock(statements, node.body); } - function visitBinaryExpression(node: BinaryExpression): Expression { - // If we are here it is because this is a destructuring assignment. - // TODO(rbuckton): Determine whether we need to save the value. - return flattenDestructuringAssignment(node, /*needsValue*/ true, hoistVariableDeclaration, visitor); + /** + * Visits an ExpressionStatement that contains a destructuring assignment. + * + * @param node An ExpressionStatement node. + */ + function visitExpressionStatement(node: ExpressionStatement): ExpressionStatement { + // If we are here it is most likely because our expression is a destructuring assignment. + switch (node.expression.kind) { + case SyntaxKind.ParenthesizedExpression: + return createStatement( + visitParenthesizedExpression(node.expression, /*needsDestructuringValue*/ false), + /*location*/ node + ); + + case SyntaxKind.BinaryExpression: + return createStatement( + visitBinaryExpression(node.expression, /*needsDestructuringValue*/ false), + /*location*/ node + ); + } + + return visitEachChild(node, visitor, context); } + /** + * Visits a ParenthesizedExpression that may contain a destructuring assignment. + * + * @param node A ParenthesizedExpression node. + * @param needsDestructuringValue A value indicating whether we need to hold onto the rhs + * of a destructuring assignment. + */ + function visitParenthesizedExpression(node: ParenthesizedExpression, needsDestructuringValue: boolean): ParenthesizedExpression { + // If we are here it is most likely because our expression is a destructuring assignment. + if (needsDestructuringValue) { + switch (node.expression.kind) { + case SyntaxKind.ParenthesizedExpression: + return createParen( + visitParenthesizedExpression(node.expression, /*needsDestructuringValue*/ true), + /*location*/ node + ); + + case SyntaxKind.BinaryExpression: + return createParen( + visitBinaryExpression(node.expression, /*needsDestructuringValue*/ true), + /*location*/ node + ); + } + } + + return visitEachChild(node, visitor, context); + } + + /** + * Visits a BinaryExpression that contains a destructuring assignment. + * + * @param node A BinaryExpression node. + * @param needsDestructuringValue A value indicating whether we need to hold onto the rhs + * of a destructuring assignment. + */ + function visitBinaryExpression(node: BinaryExpression, needsDestructuringValue: boolean): Expression { + // If we are here it is because this is a destructuring assignment. + Debug.assert(isDestructuringAssignment(node)); + return flattenDestructuringAssignment(node, needsDestructuringValue, hoistVariableDeclaration, visitor); + } + + /** + * Visits a VariableDeclarationList that is block scoped (e.g. `let` or `const`). + * + * @param node A VariableDeclarationList node. + */ function visitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList { + // If we are here it is because the list is defined as `let` or `const`. + Debug.assert((node.flags & NodeFlags.BlockScoped) !== 0); + + enableSubstitutionsForBlockScopedBindings(); return setOriginalNode( createVariableDeclarationList( - flattenNodes(map(node.declarations, visitVariableDeclaration)), + flattenNodes(map(node.declarations, visitVariableDeclarationInLetDeclarationList)), /*location*/ node ), node ); } - function visitVariableDeclaration(node: VariableDeclaration): OneOrMany { + /** + * Gets a value indicating whether we should emit an explicit initializer for a variable + * declaration in a `let` declaration list. + * + * @param node A VariableDeclaration node. + */ + function shouldEmitExplicitInitializerForLetDeclaration(node: VariableDeclaration) { + // Nested let bindings might need to be initialized explicitly to preserve + // ES6 semantic: + // + // { let x = 1; } + // { let x; } // x here should be undefined. not 1 + // + // Top level bindings never collide with anything and thus don't require + // explicit initialization. As for nested let bindings there are two cases: + // + // - Nested let bindings that were not renamed definitely should be + // initialized explicitly: + // + // { let x = 1; } + // { let x; if (some-condition) { x = 1}; if (x) { /*1*/ } } + // + // Without explicit initialization code in /*1*/ can be executed even if + // some-condition is evaluated to false. + // + // - Renaming introduces fresh name that should not collide with any + // existing names, however renamed bindings sometimes also should be + // explicitly initialized. One particular case: non-captured binding + // declared inside loop body (but not in loop initializer): + // + // let x; + // for (;;) { + // let x; + // } + // + // In downlevel codegen inner 'x' will be renamed so it won't collide + // with outer 'x' however it will should be reset on every iteration as + // if it was declared anew. + // + // * Why non-captured binding? + // - Because if loop contains block scoped binding captured in some + // function then loop body will be rewritten to have a fresh scope + // on every iteration so everything will just work. + // + // * Why loop initializer is excluded? + // - Since we've introduced a fresh name it already will be undefined. + + const original = getOriginalNode(node); + Debug.assert(isVariableDeclaration(original)); + + const flags = resolver.getNodeCheckFlags(original); + const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; + const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; + const emittedAsTopLevel = + isBlockScopedContainerTopLevel(enclosingBlockScopeContainer) + || (isCapturedInFunction + && isDeclaredInLoop + && isBlock(enclosingBlockScopeContainer) + && isIterationStatement(enclosingBlockScopeContainerParent, /*lookInLabeledStatements*/ false)); + + const emitExplicitInitializer = + !emittedAsTopLevel + && enclosingBlockScopeContainer.kind !== SyntaxKind.ForInStatement + && enclosingBlockScopeContainer.kind !== SyntaxKind.ForOfStatement + && (!resolver.isDeclarationWithCollidingName(original) + || (isDeclaredInLoop + && !isCapturedInFunction + && !isIterationStatement(enclosingBlockScopeContainer, /*lookInLabeledStatements*/ false))); + + return emitExplicitInitializer; + } + + /** + * Visits a VariableDeclaration in a `let` declaration list. + * + * @param node A VariableDeclaration node. + */ + function visitVariableDeclarationInLetDeclarationList(node: VariableDeclaration) { + // For binding pattern names that lack initializers there is no point to emit + // explicit initializer since downlevel codegen for destructuring will fail + // in the absence of initializer so all binding elements will say uninitialized const name = node.name; - if (isBindingPattern(name)) { - return createNodeArrayNode( - flattenVariableDestructuring(node, /*value*/ undefined, visitor) - ); + if (isBindingPattern(name) || node.initializer) { + return visitVariableDeclaration(node); } - else { - let initializer = node.initializer; - // For binding pattern names that lack initializer there is no point to emit - // explicit initializer since downlevel codegen for destructuring will fail - // in the absence of initializer so all binding elements will say uninitialized - if (!initializer) { - const original = getOriginalNode(node); - if (isVariableDeclaration(original)) { - // Nested let bindings might need to be initialized explicitly to preserve - // ES6 semantic: - // - // { let x = 1; } - // { let x; } // x here should be undefined. not 1 - // - // Top level bindings never collide with anything and thus don't require - // explicit initialization. As for nested let bindings there are two cases: - // - // - Nested let bindings that were not renamed definitely should be - // initialized explicitly: - // - // { let x = 1; } - // { let x; if (some-condition) { x = 1}; if (x) { /*1*/ } } - // - // Without explicit initialization code in /*1*/ can be executed even if - // some-condition is evaluated to false. - // - // - Renaming introduces fresh name that should not collide with any - // existing names, however renamed bindings sometimes also should be - // explicitly initialized. One particular case: non-captured binding - // declared inside loop body (but not in loop initializer): - // - // let x; - // for (;;) { - // let x; - // } - // - // In downlevel codegen inner 'x' will be renamed so it won't collide - // with outer 'x' however it will should be reset on every iteration as - // if it was declared anew. - // - // * Why non-captured binding? - // - Because if loop contains block scoped binding captured in some - // function then loop body will be rewritten to have a fresh scope - // on every iteration so everything will just work. - // - // * Why loop initializer is excluded? - // - Since we've introduced a fresh name it already will be undefined. - const flags = resolver.getNodeCheckFlags(original); - const isCapturedInFunction = flags & NodeCheckFlags.CapturedBlockScopedBinding; - const isDeclaredInLoop = flags & NodeCheckFlags.BlockScopedBindingInLoop; - - const emittedAsTopLevel = - isBlockScopedContainerTopLevel(enclosingBlockScopeContainer) - || (isCapturedInFunction - && isDeclaredInLoop - && isBlock(enclosingBlockScopeContainer) - && isIterationStatement(enclosingBlockScopeContainerParent, /*lookInLabeledStatements*/ false)); - - const emittedAsNestedLetDeclaration = combinedNodeFlags & NodeFlags.Let && !emittedAsTopLevel; - - const emitExplicitInitializer = - emittedAsNestedLetDeclaration - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForInStatement - && enclosingBlockScopeContainer.kind !== SyntaxKind.ForOfStatement - && (!resolver.isDeclarationWithCollidingName(original) - || (isDeclaredInLoop - && !isCapturedInFunction - && !isIterationStatement(enclosingBlockScopeContainer, /*lookInLabeledStatements*/ false))); - - if (emitExplicitInitializer) { - initializer = createVoidZero(); - } - } - } - - return setOriginalNode( - createVariableDeclaration( - name, - visitNode(initializer, visitor, isExpression, /*optional*/ true), - /*location*/ node - ), - node - ); + if (shouldEmitExplicitInitializerForLetDeclaration(node)) { + const clone = getMutableClone(node); + clone.initializer = createVoidZero(); + return clone; } + + return visitEachChild(node, visitor, context); + } + + /** + * Visits a VariableDeclaration node with a binding pattern. + * + * @param node A VariableDeclaration node. + */ + function visitVariableDeclaration(node: VariableDeclaration): OneOrMany { + // If we are here it is because the name contains a binding pattern. + Debug.assert(isBindingPattern(node.name)); + + return createNodeArrayNode( + flattenVariableDestructuring(node, /*value*/ undefined, visitor) + ); } function visitLabeledStatement(node: LabeledStatement) { @@ -771,12 +1083,16 @@ namespace ts { return visitEachChild(node, visitor, context); } - function visitForInStatement(node: ForInStatement) { // TODO: Convert loop body for block scoped bindings. return visitEachChild(node, visitor, context); } + /** + * Visits a ForOfStatement and converts it into a compatible ForStatement. + * + * @param node A ForOfStatement. + */ function visitForOfStatement(node: ForOfStatement): Statement { // TODO: Convert loop body for block scoped bindings. @@ -803,7 +1119,7 @@ namespace ts { const expression = visitNode(node.expression, visitor, isExpression); const initializer = node.initializer; - const loopBodyStatements: Statement[] = []; + const statements: Statement[] = []; // In the case where the user wrote an identifier as the RHS, like this: // @@ -822,7 +1138,7 @@ namespace ts { if (firstDeclaration && isBindingPattern(firstDeclaration.name)) { // This works whether the declaration is a var, let, or const. // It will use rhsIterationValue _a[_i] as the initializer. - addNode(loopBodyStatements, + statements.push( createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList( @@ -839,7 +1155,7 @@ namespace ts { else { // The following call does not include the initializer, so we have // to emit it separately. - addNode(loopBodyStatements, + statements.push( createVariableStatement( /*modifiers*/ undefined, createVariableDeclarationList([ @@ -859,7 +1175,7 @@ namespace ts { const assignment = createAssignment(initializer, createElementAccess(rhsReference, counter)); if (isDestructuringAssignment(assignment)) { // This is a destructuring pattern, so we flatten the destructuring instead. - addNode(loopBodyStatements, + statements.push( createStatement( flattenDestructuringAssignment( assignment, @@ -871,16 +1187,16 @@ namespace ts { ); } else { - addNode(loopBodyStatements, createStatement(assignment, /*location*/ node.initializer)); + statements.push(createStatement(assignment, /*location*/ node.initializer)); } } const statement = visitNode(node.statement, visitor, isStatement); if (isBlock(statement)) { - addNodes(loopBodyStatements, statement.statements); + addRange(statements, statement.statements); } else { - addNode(loopBodyStatements, statement); + statements.push(statement); } return createFor( @@ -898,12 +1214,17 @@ namespace ts { ), createPostfixIncrement(counter, /*location*/ initializer), createBlock( - loopBodyStatements + statements ), /*location*/ node ); } + /** + * Visits an ObjectLiteralExpression with computed propety names. + * + * @param node An ObjectLiteralExpression node. + */ function visitObjectLiteralExpression(node: ObjectLiteralExpression): LeftHandSideExpression { // We are here because a ComputedPropertyName was used somewhere in the expression. const properties = node.properties; @@ -931,10 +1252,9 @@ namespace ts { addNode(expressions, createAssignment( temp, - setMultiLine( - createObjectLiteral( - visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialNonComputedProperties) - ), + createObjectLiteral( + visitNodes(properties, visitor, isObjectLiteralElement, 0, numInitialNonComputedProperties), + /*location*/ undefined, node.multiLine ) ), @@ -945,13 +1265,23 @@ namespace ts { // We need to clone the temporary identifier so that we can write it on a // new line - addNode(expressions, cloneNode(temp), node.multiLine); + addNode(expressions, getMutableClone(temp), node.multiLine); return createParen(inlineExpressions(expressions)); } + /** + * Adds the members of an object literal to an array of expressions. + * + * @param expressions An array of expressions. + * @param node An ObjectLiteralExpression node. + * @param receiver The receiver for members of the ObjectLiteralExpression. + * @param numInitialNonComputedProperties The number of initial properties without + * computed property names. + */ function addObjectLiteralMembers(expressions: Expression[], node: ObjectLiteralExpression, receiver: Identifier, numInitialNonComputedProperties: number) { const properties = node.properties; - for (let i = numInitialNonComputedProperties, len = properties.length; i < len; i++) { + const numProperties = properties.length; + for (let i = numInitialNonComputedProperties; i < numProperties; i++) { const property = properties[i]; switch (property.kind) { case SyntaxKind.GetAccessor: @@ -982,6 +1312,13 @@ namespace ts { } } + /** + * Transforms a PropertyAssignment node into an expression. + * + * @param node The ObjectLiteralExpression that contains the PropertyAssignment. + * @param property The PropertyAssignment node. + * @param receiver The receiver for the assignment. + */ function transformPropertyAssignmentToExpression(node: ObjectLiteralExpression, property: PropertyAssignment, receiver: Expression) { return createAssignment( createMemberAccessForPropertyName( @@ -993,6 +1330,13 @@ namespace ts { ); } + /** + * Transforms a ShorthandPropertyAssignment node into an expression. + * + * @param node The ObjectLiteralExpression that contains the ShorthandPropertyAssignment. + * @param property The ShorthandPropertyAssignment node. + * @param receiver The receiver for the assignment. + */ function transformShorthandPropertyAssignmentToExpression(node: ObjectLiteralExpression, property: ShorthandPropertyAssignment, receiver: Expression) { return createAssignment( createMemberAccessForPropertyName( @@ -1004,29 +1348,47 @@ namespace ts { ); } + /** + * Transforms a MethodDeclaration of an ObjectLiteralExpression into an expression. + * + * @param node The ObjectLiteralExpression that contains the MethodDeclaration. + * @param method The MethodDeclaration node. + * @param receiver The receiver for the assignment. + */ function transformObjectLiteralMethodDeclarationToExpression(node: ObjectLiteralExpression, method: MethodDeclaration, receiver: Expression) { return createAssignment( createMemberAccessForPropertyName( receiver, visitNode(method.name, visitor, isPropertyName) ), - transformFunctionLikeToExpression(method, /*location*/ method), + transformFunctionLikeToExpression(method, /*location*/ method, /*name*/ undefined), /*location*/ method ); } + /** + * Visits a MethodDeclaration of an ObjectLiteralExpression and transforms it into a + * PropertyAssignment. + * + * @param node A MethodDeclaration node. + */ function visitMethodDeclaration(node: MethodDeclaration): ObjectLiteralElement { // We should only get here for methods on an object literal with regular identifier names. // Methods on classes are handled in visitClassDeclaration/visitClassExpression. // Methods with computed property names are handled in visitObjectLiteralExpression. - Debug.assert(isIdentifier(node.name), `Unexpected node kind: ${formatSyntaxKind(node.name.kind)}.`); + Debug.assert(!isComputedPropertyName(node.name)); return createPropertyAssignment( node.name, - transformFunctionLikeToExpression(node, /*location*/ node), + transformFunctionLikeToExpression(node, /*location*/ node, /*name*/ undefined), /*location*/ node ); } + /** + * Visits a ShorthandPropertyAssignment and transforms it into a PropertyAssignment. + * + * @param node A ShorthandPropertyAssignment node. + */ function visitShorthandPropertyAssignment(node: ShorthandPropertyAssignment): ObjectLiteralElement { return createPropertyAssignment( node.name, @@ -1035,197 +1397,148 @@ namespace ts { ); } + /** + * Visits an ArrayLiteralExpression that contains a spread element. + * + * @param node An ArrayLiteralExpression node. + */ function visitArrayLiteralExpression(node: ArrayLiteralExpression): Expression { - // We are here either because SuperKeyword was used somewhere in the expression, or - // because we contain a SpreadElementExpression. - if (node.transformFlags & TransformFlags.ContainsSpreadElementExpression) { - return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, node.multiLine); - } - else { - // We don't handle SuperKeyword here, so fall back. - return visitEachChild(node, visitor, context); - } + // We are here because we contain a SpreadElementExpression. + return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, node.multiLine); } + /** + * Visits a CallExpression that contains either a spread element or `super`. + * + * @param node a CallExpression. + */ function visitCallExpression(node: CallExpression): LeftHandSideExpression { // We are here either because SuperKeyword was used somewhere in the expression, or // because we contain a SpreadElementExpression. - const { target, thisArg } = transformCallTarget(node.expression); + + const { target, thisArg } = createCallBinding(node); if (node.transformFlags & TransformFlags.ContainsSpreadElementExpression) { + // [source] + // f(...a, b) + // x.m(...a, b) + // super(...a, b) + // super.m(...a, b) // in static + // super.m(...a, b) // in instance + // + // [output] + // f.apply(void 0, a.concat([b])) + // (_a = x).m.apply(_a, a.concat([b])) + // _super.apply(this, a.concat([b])) + // _super.m.apply(this, a.concat([b])) + // _super.prototype.m.apply(this, a.concat([b])) + return createFunctionApply( - target, - thisArg, + visitNode(target, visitor, isExpression), + visitNode(thisArg, visitor, isExpression), transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false) ); } else { - Debug.assert(isSuperCall(node)); + // [source] + // super(a) + // super.m(a) // in static + // super.m(a) // in instance + // + // [output] + // _super.call(this, a) + // _super.m.call(this, a) + // _super.prototype.m.call(this, a) + return createFunctionCall( - target, - thisArg, + visitNode(target, visitor, isExpression), + visitNode(thisArg, visitor, isExpression), visitNodes(node.arguments, visitor, isExpression), /*location*/ node ); } } + /** + * Visits a NewExpression that contains a spread element. + * + * @param node A NewExpression node. + */ function visitNewExpression(node: NewExpression): LeftHandSideExpression { - // We are here either because we contain a SpreadElementExpression. + // We are here because we contain a SpreadElementExpression. Debug.assert((node.transformFlags & TransformFlags.ContainsSpreadElementExpression) !== 0); - // Transforms `new C(...a)` into `new ((_a = C).bind.apply(_a, [void 0].concat(a)))()`. - // Transforms `new x.C(...a)` into `new ((_a = x.C).bind.apply(_a, [void 0].concat(a)))()`. - const { target, thisArg } = transformCallTarget(createPropertyAccess(node.expression, "bind")); + // [source] + // new C(...a) + // + // [output] + // new ((_a = C).bind.apply(_a, [void 0].concat(a)))() + + const { target, thisArg } = createCallBinding(createPropertyAccess(node.expression, "bind")); return createNew( - createParen( - createFunctionApply( - target, - thisArg, - transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, createVoidZero()) - ) + createFunctionApply( + visitNode(target, visitor, isExpression), + thisArg, + transformAndSpreadElements(createNodeArray([createVoidZero(), ...node.arguments]), /*needsUniqueCopy*/ false, /*multiLine*/ false) ), [] ); } - interface CallTarget { - target: Expression; - thisArg: Expression; - } + /** + * Transforms an array of Expression nodes that contains a SpreadElementExpression. + * + * @param elements The array of Expression nodes. + * @param needsUniqueCopy A value indicating whether to ensure that the result is a fresh array. + * @param multiLine A value indicating whether the result should be emitted on multiple lines. + */ + function transformAndSpreadElements(elements: NodeArray, needsUniqueCopy: boolean, multiLine: boolean): Expression { + // [source] + // [a, ...b, c] + // + // [output] + // [a].concat(b, [c]) - function transformCallTarget(expression: Expression): CallTarget { - const callee = skipParentheses(expression); - switch (callee.kind) { - case SyntaxKind.PropertyAccessExpression: - return transformPropertyAccessCallTarget(callee); - - case SyntaxKind.ElementAccessExpression: - return transformElementAccessCallTarget(callee); - - case SyntaxKind.SuperKeyword: - return transformSuperCallTarget(callee); - - default: - const thisArg = createVoidZero(); - const target = visitNode(expression, visitor, isExpression); - return { target, thisArg }; - } - } - - function transformPropertyAccessCallTarget(node: PropertyAccessExpression): CallTarget { - if (node.expression.kind === SyntaxKind.SuperKeyword) { - // For `super.b()`, target is either `_super.b` (for static members) or - // `_super.prototype.b` (for instance members), and thisArg is `this`. - const thisArg = createThis(/*location*/ node.expression); - const target = createPropertyAccess( - visitSuperKeyword(node.expression), - node.name - ); - - return { target, thisArg }; - } - else { - // For `a.b()`, target is `(_a = a).b` and thisArg is `_a`. - const thisArg = createTempVariable(); - const target = createPropertyAccess( - createAssignment( - thisArg, - visitNode(node.expression, visitor, isExpression) - ), - node.name - ); - - return { target, thisArg }; - } - } - - function transformElementAccessCallTarget(node: ElementAccessExpression): CallTarget { - if (node.expression.kind === SyntaxKind.SuperKeyword) { - // For `super[b]()`, target is either `_super[b]` (for static members) or - // `_super.prototype[b]` (for instance members), and thisArg is `this`. - const thisArg = createThis(/*location*/ node.expression); - const target = createElementAccess( - visitSuperKeyword(node.expression), - visitNode(node.argumentExpression, visitor, isExpression) - ); - - return { target, thisArg }; - } - else { - // For `a[b]()`, expression is `(_a = a)[b]` and thisArg is `_a`. - const thisArg = createTempVariable(); - const target = createElementAccess( - createAssignment( - thisArg, - visitNode(node.expression, visitor, isExpression) - ), - visitNode(node.argumentExpression, visitor, isExpression) - ); - - return { target, thisArg }; - } - } - - function transformSuperCallTarget(node: PrimaryExpression): CallTarget { - // For `super()`, expression is `_super` and thisArg is `this`. - const thisArg = createThis(/*location*/ node); - const target = createIdentifier("_super"); - return { target, thisArg }; - } - - function transformAndSpreadElements(elements: NodeArray, needsUniqueCopy: boolean, multiLine: boolean, leadingExpression?: Expression): Expression { - const segments: Expression[] = []; - addNode(segments, leadingExpression); - - const length = elements.length; - let start = 0; - for (let i = 0; i < length; i++) { - const element = elements[i]; - if (isSpreadElementExpression(element)) { - if (i > start) { - addNode(segments, - setMultiLine( - createArrayLiteral( - visitNodes(elements, visitor, isExpression, start, i) - ), - multiLine - ) - ); - } - - addNode(segments, visitNode(element.expression, visitor, isExpression)); - start = i + 1; - } - } - - if (start < length) { - addNode(segments, - setMultiLine( - createArrayLiteral( - visitNodes(elements, visitor, isExpression, start, length) - ), - multiLine - ) - ); - } + // Map spans of spread expressions into their expressions and spans of other + // expressions into an array literal. + const segments = flatten( + spanMap(elements, isSpreadElementExpression, (chunk: Expression[], isSpread: boolean) => isSpread + ? map(chunk, visitExpressionOfSpreadElement) + : createArrayLiteral(visitNodes(createNodeArray(chunk), visitor, isExpression), /*location*/ undefined, multiLine))); if (segments.length === 1) { - if (!leadingExpression && needsUniqueCopy && isSpreadElementExpression(elements[0])) { - return createArraySlice(segments[0]); - } - - return segments[0]; + return needsUniqueCopy && isSpreadElementExpression(elements[0]) + ? createArraySlice(segments[0]) + : segments[0]; } // Rewrite using the pattern .concat(, , ...) return createArrayConcat(segments.shift(), segments); } + /** + * Transforms the expression of a SpreadElementExpression node. + * + * @param node A SpreadElementExpression node. + */ + function visitExpressionOfSpreadElement(node: SpreadElementExpression) { + return visitNode(node.expression, visitor, isExpression); + } + + /** + * Visits a template literal. + * + * @param node A template literal. + */ function visitTemplateLiteral(node: LiteralExpression): LeftHandSideExpression { return createLiteral(node.text); } - function visitTaggedTemplateExpression(node: TaggedTemplateExpression): LeftHandSideExpression { + /** + * Visits a TaggedTemplateExpression node. + * + * @param node A TaggedTemplateExpression node. + */ + function visitTaggedTemplateExpression(node: TaggedTemplateExpression) { // Visit the tag expression const tag = visitNode(node.tag, visitor, isExpression); @@ -1239,31 +1552,31 @@ namespace ts { const rawStrings: Expression[] = []; const template = node.template; if (isNoSubstitutionTemplateLiteral(template)) { - addNode(cookedStrings, createLiteral(template.text)); - addNode(rawStrings, getRawLiteral(template)); + cookedStrings.push(createLiteral(template.text)); + rawStrings.push(getRawLiteral(template)); } else { - addNode(cookedStrings, createLiteral(template.head.text)); - addNode(rawStrings, getRawLiteral(template.head)); + cookedStrings.push(createLiteral(template.head.text)); + rawStrings.push(getRawLiteral(template.head)); for (const templateSpan of template.templateSpans) { - addNode(cookedStrings, createLiteral(templateSpan.literal.text)); - addNode(rawStrings, getRawLiteral(templateSpan.literal)); - addNode(templateArguments, visitNode(templateSpan.expression, visitor, isExpression)); + cookedStrings.push(createLiteral(templateSpan.literal.text)); + rawStrings.push(getRawLiteral(templateSpan.literal)); + templateArguments.push(visitNode(templateSpan.expression, visitor, isExpression)); } } - return createParen( - inlineExpressions([ - createAssignment(temp, createArrayLiteral(cookedStrings)), - createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)), - createCall( - tag, - templateArguments - ) - ]) - ); + return inlineExpressions([ + createAssignment(temp, createArrayLiteral(cookedStrings)), + createAssignment(createPropertyAccess(temp, "raw"), createArrayLiteral(rawStrings)), + createCall(tag, templateArguments) + ]); } + /** + * Creates an ES5 compatible literal from an ES6 template literal. + * + * @param node The ES6 template literal. + */ function getRawLiteral(node: LiteralLikeNode) { // Find original source text, since we need to emit the raw strings of the tagged template. // The raw strings contain the (escaped) strings of what the user wrote. @@ -1285,10 +1598,15 @@ namespace ts { return createLiteral(text); } + /** + * Visits a TemplateExpression node. + * + * @param node A TemplateExpression node. + */ function visitTemplateExpression(node: TemplateExpression): Expression { const expressions: Expression[] = []; addTemplateHead(expressions, node); - addTemplateSpans(expressions, node.templateSpans); + addTemplateSpans(expressions, node); // createAdd will check if each expression binds less closely than binary '+'. // If it does, it wraps the expression in parentheses. Otherwise, something like @@ -1307,6 +1625,11 @@ namespace ts { return expression; } + /** + * Gets a value indicating whether we need to include the head of a TemplateExpression. + * + * @param node A TemplateExpression node. + */ function shouldAddTemplateHead(node: TemplateExpression) { // If this expression has an empty head literal and the first template span has a non-empty // literal, then emitting the empty head literal is not necessary. @@ -1329,28 +1652,43 @@ namespace ts { return node.head.text.length !== 0 || node.templateSpans[0].literal.text.length === 0; } + /** + * Adds the head of a TemplateExpression to an array of expressions. + * + * @param expressions An array of expressions. + * @param node A TemplateExpression node. + */ function addTemplateHead(expressions: Expression[], node: TemplateExpression): void { if (!shouldAddTemplateHead(node)) { return; } - addNode(expressions, createLiteral(node.head.text)); + expressions.push(createLiteral(node.head.text)); } - function addTemplateSpans(expressions: Expression[], nodes: TemplateSpan[]): void { - for (const node of nodes) { - addNode(expressions, visitNode(node.expression, visitor, isExpression)); + /** + * Visits and adds the template spans of a TemplateExpression to an array of expressions. + * + * @param expressions An array of expressions. + * @param node A TemplateExpression node. + */ + function addTemplateSpans(expressions: Expression[], node: TemplateExpression): void { + for (const span of node.templateSpans) { + expressions.push(visitNode(span.expression, visitor, isExpression)); // Only emit if the literal is non-empty. // The binary '+' operator is left-associative, so the first string concatenation // with the head will force the result up to this point to be a string. // Emitting a '+ ""' has no semantic effect for middles and tails. - if (node.literal.text.length !== 0) { - addNode(expressions, createLiteral(node.literal.text)); + if (span.literal.text.length !== 0) { + expressions.push(createLiteral(span.literal.text)); } } } + /** + * Visits the `super` keyword + */ function visitSuperKeyword(node: PrimaryExpression): LeftHandSideExpression { return containingNonArrowFunction && isClassElement(containingNonArrowFunction) @@ -1360,89 +1698,120 @@ namespace ts { } function visitSourceFileNode(node: SourceFile): SourceFile { - const clone = cloneNode(node, node, node.flags, /*parent*/ undefined, node); + const [prologue, remaining] = span(node.statements, isPrologueDirective); const statements: Statement[] = []; startLexicalEnvironment(); - const statementOffset = addPrologueDirectives(statements, node.statements); + addRange(statements, prologue); addCaptureThisForNodeIfNeeded(statements, node); - addNodes(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); - addNodes(statements, endLexicalEnvironment()); - clone.statements = createNodeArray(statements, node.statements); + addRange(statements, visitNodes(createNodeArray(remaining), visitor, isStatement)); + addRange(statements, endLexicalEnvironment()); + const clone = cloneNode(node, node, node.flags, /*parent*/ undefined, node); + clone.statements = createNodeArray(statements, /*location*/ node.statements); return clone; } - function addPrologueDirectives(to: Statement[], from: NodeArray): number { - for (let i = 0; i < from.length; i++) { - if (isPrologueDirective(from[i])) { - addNode(to, from[i]); - } - else { - return i; - } - } - - return from.length; - } - + /** + * Called by the printer just before a node is printed. + * + * @param node The node to be printed. + */ function onBeforeEmitNode(node: Node) { previousOnBeforeEmitNode(node); - if (containingFunctionStack && isFunctionLike(node)) { - containingFunctionStack.push(node); + if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isFunctionLike(node)) { + // If we are tracking a captured `this`, push a bit that indicates whether the + // containing function is an arrow function. + pushContainingFunction(node.kind === SyntaxKind.ArrowFunction); } } + /** + * Called by the printer just after a node is printed. + * + * @param node The node that was printed. + */ function onAfterEmitNode(node: Node) { previousOnAfterEmitNode(node); - if (containingFunctionStack && isFunctionLike(node)) { - containingFunctionStack.pop(); + if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isFunctionLike(node)) { + // If we are tracking a captured `this`, pop the last containing function bit. + popContainingFunction(); } } - function enableExpressionSubstitutionForCapturedThis() { - if (!hasEnabledExpressionSubstitutionForCapturedThis) { - hasEnabledExpressionSubstitutionForCapturedThis = true; - enableExpressionSubstitution(SyntaxKind.ThisKeyword); - enableEmitNotification(SyntaxKind.Constructor); - enableEmitNotification(SyntaxKind.MethodDeclaration); - enableEmitNotification(SyntaxKind.GetAccessor); - enableEmitNotification(SyntaxKind.SetAccessor); - enableEmitNotification(SyntaxKind.ArrowFunction); - enableEmitNotification(SyntaxKind.FunctionExpression); - enableEmitNotification(SyntaxKind.FunctionDeclaration); - containingFunctionStack = []; + /** + * Enables a more costly code path for substitutions when we determine a source file + * contains block-scoped bindings (e.g. `let` or `const`). + */ + function enableSubstitutionsForBlockScopedBindings() { + if ((enabledSubstitutions & ES6SubstitutionFlags.BlockScopedBindings) === 0) { + enabledSubstitutions |= ES6SubstitutionFlags.BlockScopedBindings; + context.enableExpressionSubstitution(SyntaxKind.Identifier); } } + /** + * Enables a more costly code path for substitutions when we determine a source file + * contains a captured `this`. + */ + function enableSubstitutionsForCapturedThis() { + if ((enabledSubstitutions & ES6SubstitutionFlags.CapturedThis) === 0) { + enabledSubstitutions |= ES6SubstitutionFlags.CapturedThis; + containingFunctionDepth = 0; + context.enableExpressionSubstitution(SyntaxKind.ThisKeyword); + context.enableEmitNotification(SyntaxKind.Constructor); + context.enableEmitNotification(SyntaxKind.MethodDeclaration); + context.enableEmitNotification(SyntaxKind.GetAccessor); + context.enableEmitNotification(SyntaxKind.SetAccessor); + context.enableEmitNotification(SyntaxKind.ArrowFunction); + context.enableEmitNotification(SyntaxKind.FunctionExpression); + context.enableEmitNotification(SyntaxKind.FunctionDeclaration); + } + } + + /** + * Hooks substitutions for non-expression identifiers. + */ function substituteIdentifier(node: Identifier) { node = previousIdentifierSubstitution(node); - const original = getOriginalNode(node); - if (isIdentifier(original) && isNameOfDeclarationWithCollidingName(original)) { - return getGeneratedNameForNode(original); + // Only substitute the identifier if we have enabled substitutions for block-scoped + // bindings. + if (enabledSubstitutions & ES6SubstitutionFlags.BlockScopedBindings) { + const original = getOriginalNode(node); + if (isIdentifier(original) && !nodeIsSynthesized(original) && original.parent && isNameOfDeclarationWithCollidingName(original)) { + return getGeneratedNameForNode(original); + } } return node; } + /** + * Determines whether a name is the name of a declaration with a colliding name. + * NOTE: This function expects to be called with an original source tree node. + * + * @param node An original source tree node. + */ function isNameOfDeclarationWithCollidingName(node: Identifier) { const parent = node.parent; - if (parent) { - switch (parent.kind) { - case SyntaxKind.BindingElement: - case SyntaxKind.ClassDeclaration: - case SyntaxKind.EnumDeclaration: - case SyntaxKind.VariableDeclaration: - return (parent).name === node - && resolver.isDeclarationWithCollidingName(parent); - } + switch (parent.kind) { + case SyntaxKind.BindingElement: + case SyntaxKind.ClassDeclaration: + case SyntaxKind.EnumDeclaration: + case SyntaxKind.VariableDeclaration: + return (parent).name === node + && resolver.isDeclarationWithCollidingName(parent); } return false; } - + /** + * Substitutes an expression. + * + * @param node An Expression node. + */ function substituteExpression(node: Expression): Expression { node = previousExpressionSubstitution(node); switch (node.kind) { @@ -1456,29 +1825,93 @@ namespace ts { return node; } + /** + * Substitutes an expression identifier. + * + * @param node An Identifier node. + */ function substituteExpressionIdentifier(node: Identifier): Identifier { - const original = getOriginalNode(node); - if (isIdentifier(original)) { - const declaration = resolver.getReferencedDeclarationWithCollidingName(original); - if (declaration) { - return getGeneratedNameForNode(declaration.name); + if (enabledSubstitutions & ES6SubstitutionFlags.BlockScopedBindings) { + const original = getOriginalNode(node); + if (isIdentifier(original)) { + const declaration = resolver.getReferencedDeclarationWithCollidingName(original); + if (declaration) { + return getGeneratedNameForNode(declaration.name); + } } } return node; } + /** + * Substitutes `this` when contained within an arrow function. + * + * @param node The ThisKeyword node. + */ function substituteThisKeyword(node: PrimaryExpression): PrimaryExpression { - if (containingFunctionStack) { - const containingFunction = lastOrUndefined(containingFunctionStack); - if (containingFunction && getOriginalNode(containingFunction).kind === SyntaxKind.ArrowFunction) { - return createIdentifier("_this"); - } + if (enabledSubstitutions & ES6SubstitutionFlags.CapturedThis && isContainedInArrowFunction()) { + return createIdentifier("_this", /*location*/ node); } return node; } + /** + * Pushes a value onto a stack that indicates whether we are currently printing a node + * within an arrow function. This is used to determine whether we need to capture `this`. + * + * @param isArrowFunction A value indicating whether the current function container is + * an arrow function. + */ + function pushContainingFunction(isArrowFunction: boolean) { + // Encode whether the containing function is an arrow function in the first 31 bits of + // an integer. If the stack grows beyond a depth of 31 functions, use an array. + if (containingFunctionDepth > 0 && containingFunctionDepth % 31 === 0) { + if (!containingFunctionStack) { + containingFunctionStack = [containingFunctionState]; + } + else { + containingFunctionStack.push(containingFunctionState); + } + + containingFunctionState = 0; + } + + if (isArrowFunction) { + containingFunctionState |= 1 << (containingFunctionDepth % 31); + } + + containingFunctionDepth++; + } + + /** + * Pops a value off of the containing function stack. + */ + function popContainingFunction() { + if (containingFunctionDepth > 0) { + containingFunctionDepth--; + if (containingFunctionDepth === 0) { + containingFunctionState = 0; + } + else if (containingFunctionDepth % 31 === 0) { + containingFunctionState = containingFunctionStack.pop(); + } + else { + containingFunctionState &= ~(1 << containingFunctionDepth % 31); + } + } + } + + /** + * Gets a value indicating whether we are currently printing a node inside of an arrow + * function. + */ + function isContainedInArrowFunction() { + return containingFunctionDepth > 0 + && containingFunctionState & (1 << (containingFunctionDepth - 1) % 31); + } + function getDeclarationName(node: ClassExpression | ClassDeclaration | FunctionDeclaration) { return node.name ? getSynthesizedClone(node.name) : getGeneratedNameForNode(node); } diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 84bfb04976..a32b13a255 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -44,7 +44,6 @@ namespace ts { let currentScope: SourceFile | Block | ModuleBlock | CaseBlock; let currentParent: Node; let currentNode: Node; - let combinedNodeFlags: NodeFlags; /** * Keeps track of whether expression substitution has been enabled for specific edge cases. @@ -104,7 +103,6 @@ namespace ts { const savedCurrentScope = currentScope; const savedCurrentParent = currentParent; const savedCurrentNode = currentNode; - const savedCombinedNodeFlags = combinedNodeFlags; // Handle state changes before visiting a node. onBeforeVisitNode(node); @@ -116,7 +114,6 @@ namespace ts { currentScope = savedCurrentScope; currentParent = savedCurrentParent; currentNode = savedCurrentNode; - combinedNodeFlags = savedCombinedNodeFlags; return node; } @@ -392,8 +389,6 @@ namespace ts { currentParent = currentNode; currentNode = node; - combinedNodeFlags = combineNodeFlags(currentNode, currentParent, combinedNodeFlags); - switch (node.kind) { case SyntaxKind.SourceFile: case SyntaxKind.CaseBlock: @@ -2758,7 +2753,7 @@ namespace ts { function substituteCallExpression(node: CallExpression): Expression { const expression = node.expression; - if (isSuperPropertyOrElementAccess(expression)) { + if (isSuperProperty(expression)) { const flags = getSuperContainerAsyncMethodFlags(); if (flags) { const argumentExpression = isPropertyAccessExpression(expression) diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 5cced24249..da0f983298 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2761,18 +2761,19 @@ namespace ts { ContainsES7 = 1 << 5, ES6 = 1 << 6, ContainsES6 = 1 << 7, + DestructuringAssignment = 1 << 8, // Markers // - Flags used to indicate that a subtree contains a specific transformation. - ContainsDecorators = 1 << 8, - ContainsPropertyInitializer = 1 << 9, - ContainsLexicalThis = 1 << 10, - ContainsCapturedLexicalThis = 1 << 11, - ContainsDefaultValueAssignments = 1 << 12, - ContainsParameterPropertyAssignments = 1 << 13, - ContainsSpreadElementExpression = 1 << 14, - ContainsComputedPropertyName = 1 << 15, - ContainsBlockScopedBinding = 1 << 16, + ContainsDecorators = 1 << 9, + ContainsPropertyInitializer = 1 << 10, + ContainsLexicalThis = 1 << 11, + ContainsCapturedLexicalThis = 1 << 12, + ContainsDefaultValueAssignments = 1 << 13, + ContainsParameterPropertyAssignments = 1 << 14, + ContainsSpreadElementExpression = 1 << 15, + ContainsComputedPropertyName = 1 << 16, + ContainsBlockScopedBinding = 1 << 17, // Assertions // - Bitmasks that are used to assert facts about the syntax of a node and its subtree. @@ -2784,7 +2785,7 @@ namespace ts { // Scope Exclusions // - Bitmasks that exclude flags from propagating out of a specific context // into the subtree flags of their container. - NodeExcludes = TypeScript | Jsx | ES7 | ES6, + NodeExcludes = TypeScript | Jsx | ES7 | ES6 | DestructuringAssignment, ArrowFunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, FunctionExcludes = ContainsDecorators | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsParameterPropertyAssignments | ContainsBlockScopedBinding, ConstructorExcludes = ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding, diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 11aa14cf5b..9568a86350 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -491,19 +491,6 @@ namespace ts { return node; } - /** - * Combines the flags of a node with the combined flags of its parent if they can be combined. - */ - export function combineNodeFlags(node: Node, parentNode: Node, previousNodeFlags: NodeFlags) { - if ((node.kind === SyntaxKind.VariableDeclarationList && parentNode.kind === SyntaxKind.VariableStatement) || - (node.kind === SyntaxKind.VariableDeclaration && parentNode.kind === SyntaxKind.VariableDeclarationList) || - (node.kind === SyntaxKind.BindingElement)) { - return node.flags | previousNodeFlags; - } - - return node.flags; - } - // Returns the node flags for this node and all relevant parent nodes. This is done so that // nodes like variable declarations and binding elements can returned a view of their flags // that includes the modifiers from their container. i.e. flags like export/declare aren't @@ -948,19 +935,20 @@ namespace ts { /** * Determines whether a node is a property or element access expression for super. */ - export function isSuperPropertyOrElementAccess(node: Node): node is (PropertyAccessExpression | ElementAccessExpression) { + export function isSuperProperty(node: Node): node is (PropertyAccessExpression | ElementAccessExpression) { return (node.kind === SyntaxKind.PropertyAccessExpression || node.kind === SyntaxKind.ElementAccessExpression) && (node).expression.kind === SyntaxKind.SuperKeyword; } - /** - * Determines whether a node is a call to either `super`, or a super property or element access. - */ + export function isSuperPropertyCall(node: Node): node is CallExpression { + return node.kind === SyntaxKind.CallExpression + && isSuperProperty((node).expression); + } + export function isSuperCall(node: Node): node is CallExpression { return node.kind === SyntaxKind.CallExpression - && ((node).expression.kind === SyntaxKind.SuperKeyword - || isSuperPropertyOrElementAccess((node).expression)); + && (node).expression.kind === SyntaxKind.SuperKeyword; } export function getEntityNameFromTypeNode(node: TypeNode): EntityName | Expression { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 40568b6fa6..5bbf82865e 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -134,7 +134,7 @@ namespace ts { { name: "arguments", test: isExpression }, ], [SyntaxKind.NewExpression]: [ - { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForAccess }, + { name: "expression", test: isLeftHandSideExpression, parenthesize: parenthesizeForNew }, { name: "typeArguments", test: isTypeNode }, { name: "arguments", test: isExpression }, ], @@ -630,7 +630,7 @@ namespace ts { if (updated !== undefined || visited !== value) { if (updated === undefined) { - updated = getMutableNode(node); + updated = getMutableClone(node); updated.flags &= ~NodeFlags.Modifier; }