diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index ed70444bd9b..11c8773718a 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3093,32 +3093,24 @@ namespace ts { function computeCallExpression(node: CallExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; + const callee = skipOuterExpressions(node.expression); const expression = node.expression; if (node.typeArguments) { transformFlags |= TransformFlags.AssertTypeScript; } - if (subtreeFlags & TransformFlags.ContainsRestOrSpread - || (expression.transformFlags & (TransformFlags.Super | TransformFlags.ContainsSuper))) { + if (subtreeFlags & TransformFlags.ContainsRestOrSpread || isSuperOrSuperProperty(callee)) { // If the this node contains a SpreadExpression, or is a super call, then it is an ES6 // node. transformFlags |= TransformFlags.AssertES2015; - // super property or element accesses could be inside lambdas, etc, and need a captured `this`, - // while super keyword for super calls (indicated by TransformFlags.Super) does not (since it can only be top-level in a constructor) - if (expression.transformFlags & TransformFlags.ContainsSuper) { + if (isSuperProperty(callee)) { transformFlags |= TransformFlags.ContainsLexicalThis; } } if (expression.kind === SyntaxKind.ImportKeyword) { transformFlags |= TransformFlags.ContainsDynamicImport; - - // A dynamic 'import()' call that contains a lexical 'this' will - // require a captured 'this' when emitting down-level. - if (subtreeFlags & TransformFlags.ContainsLexicalThis) { - transformFlags |= TransformFlags.ContainsCapturedLexicalThis; - } } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3191,7 +3183,7 @@ namespace ts { // If a parameter has an initializer, a binding pattern or a dotDotDot token, then // it is ES6 syntax and its container must emit default value assignments or parameter destructuring downlevel. if (subtreeFlags & TransformFlags.ContainsBindingPattern || initializer || dotDotDotToken) { - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsDefaultValueAssignments; + transformFlags |= TransformFlags.AssertES2015; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3202,7 +3194,6 @@ namespace ts { let transformFlags = subtreeFlags; const expression = node.expression; const expressionKind = expression.kind; - const expressionTransformFlags = expression.transformFlags; // If the node is synthesized, it means the emitter put the parentheses there, // not the user. If we didn't want them, the emitter would not have put them @@ -3212,12 +3203,6 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - // If the expression of a ParenthesizedExpression is a destructuring assignment, - // then the ParenthesizedExpression is a destructuring assignment. - if (expressionTransformFlags & TransformFlags.DestructuringAssignment) { - transformFlags |= TransformFlags.DestructuringAssignment; - } - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.OuterExpressionExcludes; } @@ -3241,12 +3226,6 @@ namespace ts { || node.typeParameters) { transformFlags |= TransformFlags.AssertTypeScript; } - - if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { - // A computed property name containing `this` might need to be rewritten, - // so propagate the ContainsLexicalThis flag upward. - transformFlags |= TransformFlags.ContainsLexicalThis; - } } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3264,12 +3243,6 @@ namespace ts { transformFlags |= TransformFlags.AssertTypeScript; } - if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { - // A computed property name containing `this` might need to be rewritten, - // so propagate the ContainsLexicalThis flag upward. - transformFlags |= TransformFlags.ContainsLexicalThis; - } - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.ClassExcludes; } @@ -3374,7 +3347,7 @@ namespace ts { } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.MethodOrAccessorExcludes; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); } function computeAccessor(node: AccessorDeclaration, subtreeFlags: TransformFlags) { @@ -3396,7 +3369,7 @@ namespace ts { } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.MethodOrAccessorExcludes; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.MethodOrAccessorExcludes); } function computePropertyDeclaration(node: PropertyDeclaration, subtreeFlags: TransformFlags) { @@ -3410,7 +3383,7 @@ namespace ts { } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; - return transformFlags & ~TransformFlags.NodeExcludes; + return propagatePropertyNameFlags(node.name, transformFlags & ~TransformFlags.PropertyExcludes); } function computeFunctionDeclaration(node: FunctionDeclaration, subtreeFlags: TransformFlags) { @@ -3444,13 +3417,6 @@ namespace ts { transformFlags |= TransformFlags.AssertES2018; } - // If a FunctionDeclaration's subtree has marked the container as needing to capture the - // lexical this, or the function contains parameters with initializers, then this node is - // ES6 syntax. - if (subtreeFlags & TransformFlags.ES2015FunctionSyntaxMask) { - transformFlags |= TransformFlags.AssertES2015; - } - // If a FunctionDeclaration is generator function and is the body of a // transformed async function, then this node can be transformed to a // down-level generator. @@ -3486,14 +3452,6 @@ namespace ts { transformFlags |= TransformFlags.AssertES2018; } - - // If a FunctionExpression's subtree has marked the container as needing to capture the - // lexical this, or the function contains parameters with initializers, then this node is - // ES6 syntax. - if (subtreeFlags & TransformFlags.ES2015FunctionSyntaxMask) { - transformFlags |= TransformFlags.AssertES2015; - } - // If a FunctionExpression is generator function and is the body of a // transformed async function, then this node can be transformed to a // down-level generator. @@ -3527,11 +3485,6 @@ namespace ts { transformFlags |= TransformFlags.AssertES2018; } - // If an ArrowFunction contains a lexical this, its container must capture the lexical this. - if (subtreeFlags & TransformFlags.ContainsLexicalThis) { - transformFlags |= TransformFlags.ContainsCapturedLexicalThis; - } - node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.ArrowFunctionExcludes; } @@ -3541,11 +3494,10 @@ namespace ts { // If a PropertyAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. - if (transformFlags & TransformFlags.Super) { - transformFlags ^= TransformFlags.Super; + if (node.expression.kind === SyntaxKind.SuperKeyword) { // super inside of an async function requires hoisting the super access (ES2017). // same for super inside of an async generator, which is ES2018. - transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; + transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3554,16 +3506,13 @@ namespace ts { function computeElementAccess(node: ElementAccessExpression, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; - const expression = node.expression; - const expressionFlags = expression.transformFlags; // We do not want to aggregate flags from the argument expression for super/this capturing // If an ElementAccessExpression starts with a super keyword, then it is // ES6 syntax, and requires a lexical `this` binding. - if (expressionFlags & TransformFlags.Super) { - transformFlags &= ~TransformFlags.Super; + if (node.expression.kind === SyntaxKind.SuperKeyword) { // super inside of an async function requires hoisting the super access (ES2017). // same for super inside of an async generator, which is ES2018. - transformFlags |= TransformFlags.ContainsSuper | TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; + transformFlags |= TransformFlags.ContainsES2017 | TransformFlags.ContainsES2018; } node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; @@ -3572,7 +3521,7 @@ namespace ts { function computeVariableDeclaration(node: VariableDeclaration, subtreeFlags: TransformFlags) { let transformFlags = subtreeFlags; - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; + transformFlags |= TransformFlags.AssertES2015 | TransformFlags.ContainsBindingPattern; // TODO(rbuckton): Why are these set unconditionally? // A VariableDeclaration containing ObjectRest is ES2018 syntax if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { @@ -3634,15 +3583,7 @@ namespace ts { } function computeExpressionStatement(node: ExpressionStatement, subtreeFlags: TransformFlags) { - let transformFlags = subtreeFlags; - - // 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.AssertES2015; - } - + const transformFlags = subtreeFlags; node.transformFlags = transformFlags | TransformFlags.HasComputedFlags; return transformFlags & ~TransformFlags.NodeExcludes; } @@ -3815,17 +3756,6 @@ namespace ts { // This is so that they can flow through PropertyName transforms unaffected. // Instead, we mark the container as ES6, so that it can properly handle the transform. transformFlags |= TransformFlags.ContainsComputedPropertyName; - if (subtreeFlags & TransformFlags.ContainsLexicalThis) { - // A computed method name like `[this.getName()](x: string) { ... }` needs to - // distinguish itself from the normal case of a method body containing `this`: - // `this` inside a method doesn't need to be rewritten (the method provides `this`), - // whereas `this` inside a computed name *might* need to be rewritten if the class/object - // is inside an arrow function: - // `_this = this; () => class K { [_this.getName()]() { ... } }` - // To make this distinction, use ContainsLexicalThisInComputedPropertyName - // instead of ContainsLexicalThis for computed property names - transformFlags |= TransformFlags.ContainsLexicalThisInComputedPropertyName; - } break; case SyntaxKind.SpreadElement: @@ -3838,7 +3768,7 @@ namespace ts { case SyntaxKind.SuperKeyword: // This node is ES6 syntax. - transformFlags |= TransformFlags.AssertES2015 | TransformFlags.Super; + transformFlags |= TransformFlags.AssertES2015; excludeFlags = TransformFlags.OuterExpressionExcludes; // must be set to persist `Super` break; @@ -3880,12 +3810,6 @@ namespace ts { transformFlags |= TransformFlags.AssertES2015; } - if (subtreeFlags & TransformFlags.ContainsLexicalThisInComputedPropertyName) { - // A computed property name containing `this` might need to be rewritten, - // so propagate the ContainsLexicalThis flag upward. - transformFlags |= TransformFlags.ContainsLexicalThis; - } - if (subtreeFlags & TransformFlags.ContainsObjectRestOrSpread) { // If an ObjectLiteralExpression contains a spread element, then it // is an ES2018 node. @@ -3895,14 +3819,7 @@ namespace ts { break; case SyntaxKind.ArrayLiteralExpression: - case SyntaxKind.NewExpression: excludeFlags = TransformFlags.ArrayLiteralOrCallOrNewExcludes; - if (subtreeFlags & TransformFlags.ContainsRestOrSpread) { - // If the this node contains a SpreadExpression, then it is an ES6 - // node. - transformFlags |= TransformFlags.AssertES2015; - } - break; case SyntaxKind.DoStatement: @@ -3917,10 +3834,6 @@ namespace ts { break; case SyntaxKind.SourceFile: - if (subtreeFlags & TransformFlags.ContainsCapturedLexicalThis) { - transformFlags |= TransformFlags.AssertES2015; - } - break; case SyntaxKind.ReturnStatement: @@ -3938,6 +3851,10 @@ namespace ts { return transformFlags & ~excludeFlags; } + function propagatePropertyNameFlags(node: PropertyName, transformFlags: TransformFlags) { + return transformFlags | (node.transformFlags & TransformFlags.PropertyNamePropagatingFlags); + } + /** * Gets the transform flags to exclude when unioning the transform flags of a subtree. * diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index f97cec89cdf..610e527451d 100644 --- a/src/compiler/transformers/es2015.ts +++ b/src/compiler/transformers/es2015.ts @@ -145,30 +145,6 @@ namespace ts { loopOutParameters: LoopOutParameter[]; } - const enum SuperCaptureResult { - /** - * A capture may have been added for calls to 'super', but - * the caller should emit subsequent statements normally. - */ - NoReplacement, - /** - * A call to 'super()' got replaced with a capturing statement like: - * - * var _this = _super.call(...) || this; - * - * Callers should skip the current statement. - */ - ReplaceSuperCapture, - /** - * A call to 'super()' got replaced with a capturing statement like: - * - * return _super.call(...) || this; - * - * Callers should skip the current statement and avoid any returns of '_this'. - */ - ReplaceWithReturn, - } - type LoopConverter = (node: IterationStatement, outermostLabeledStatement: LabeledStatement | undefined, convertedLoopBodyStatements: Statement[] | undefined) => Statement; // Facts we track as we traverse the tree @@ -192,14 +168,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. + // NOTE: when adding a new ancestor flag, be sure to update the subtree flags below. // // Ancestor masks // - AncestorFactsMask = (ComputedPropertyName << 1) - 1, + AncestorFactsMask = (ConstructorWithCapturedSuper << 1) - 1, // We are always in *some* kind of block scope, but only specific block-scope containers are // top-level or Blocks. @@ -212,14 +188,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 | ComputedPropertyName, + FunctionExcludes = BlockScopeExcludes & ~TopLevel | ArrowFunction | AsyncFunctionBody | CapturesThis | NonStaticClassElement | ConstructorWithCapturedSuper, AsyncFunctionBodyIncludes = FunctionIncludes | AsyncFunctionBody, AsyncFunctionBodyExcludes = FunctionExcludes & ~NonStaticClassElement, // Arrow functions are lexically scoped to their container, but are new block scopes. ArrowFunctionIncludes = ArrowFunction | TopLevel, - ArrowFunctionExcludes = BlockScopeExcludes & ~TopLevel | ConstructorWithCapturedSuper | ComputedPropertyName, + 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. @@ -248,23 +224,21 @@ namespace ts { IterationStatementBlockIncludes = IterationStatementBlock, IterationStatementBlockExcludes = BlockScopeExcludes, - // Computed property names track subtree flags differently than their containing members. - ComputedPropertyNameIncludes = ComputedPropertyName, - ComputedPropertyNameExcludes = None, - // // Subtree facts // - NewTarget = 1 << 14, // Contains a 'new.target' meta-property - NewTargetInComputedPropertyName = 1 << 15, // Contains a 'new.target' meta-property in a computed property name. + NewTarget = 1 << 13, // Contains a 'new.target' meta-property + CapturedLexicalThis = 1 << 14, // Contains a lexical `this` reference captured by an arrow function. // // Subtree masks // SubtreeFactsMask = ~AncestorFactsMask, - PropagateNewTargetMask = NewTarget | NewTargetInComputedPropertyName, + + ArrowFunctionSubtreeExcludes = None, + FunctionSubtreeExcludes = NewTarget | CapturedLexicalThis, } export function transformES2015(context: TransformationContext) { @@ -370,13 +344,6 @@ namespace ts { } } - function functionBodyVisitor(node: Block): Block { - if (shouldVisitNode(node)) { - return visitBlock(node, /*isFunctionBody*/ true); - } - return node; - } - function callExpressionVisitor(node: Node): VisitResult { if (node.kind === SyntaxKind.SuperKeyword) { return visitSuperKeyword(/*isExpressionOfCall*/ true); @@ -528,22 +495,23 @@ namespace ts { function visitSourceFile(node: SourceFile): SourceFile { const ancestorFacts = enterSubtree(HierarchyFacts.SourceFileExcludes, HierarchyFacts.SourceFileIncludes); + const prologue: Statement[] = []; const statements: Statement[] = []; startLexicalEnvironment(); - let statementOffset: number | undefined = addStandardPrologue(statements, node.statements, /*ensureUseStrict*/ false); - addCaptureThisForNodeIfNeeded(statements, node); - statementOffset = addCustomPrologue(statements, node.statements, statementOffset, visitor); + let statementOffset = addStandardPrologue(prologue, node.statements, /*ensureUseStrict*/ false); + statementOffset = addCustomPrologue(prologue, node.statements, statementOffset, visitor); addRange(statements, visitNodes(node.statements, visitor, isStatement, statementOffset)); if (taggedTemplateStringDeclarations) { statements.push( createVariableStatement(/*modifiers*/ undefined, createVariableDeclarationList(taggedTemplateStringDeclarations))); } - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + mergeLexicalEnvironment(prologue, endLexicalEnvironment()); + insertCaptureThisForNodeIfNeeded(prologue, node); exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updateSourceFileNode( node, - setTextRange(createNodeArray(statements), node.statements) + setTextRange(createNodeArray(concatenate(prologue, statements)), node.statements) ); } @@ -596,6 +564,9 @@ namespace ts { } function visitThisKeyword(node: Node): Node { + if (hierarchyFacts & HierarchyFacts.ArrowFunction) { + hierarchyFacts |= HierarchyFacts.CapturedLexicalThis; + } if (convertedLoopState) { if (hierarchyFacts & HierarchyFacts.ArrowFunction) { // if the enclosing function is an ArrowFunction then we use the captured 'this' keyword. @@ -847,7 +818,7 @@ namespace ts { setEmitFlags(statement, EmitFlags.NoComments | EmitFlags.NoTokenSourceMaps); statements.push(statement); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const block = createBlock(setTextRange(createNodeArray(statements), /*location*/ node.members), /*multiLine*/ true); setEmitFlags(block, EmitFlags.NoComments); @@ -904,7 +875,7 @@ namespace ts { } statements.push(constructorFunction); - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); + exitSubtree(ancestorFacts, HierarchyFacts.FunctionSubtreeExcludes, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; } @@ -925,6 +896,28 @@ namespace ts { || []; } + function createDefaultConstructorBody(node: ClassDeclaration | ClassExpression, isDerivedClass: boolean) { + // We must be here because the user didn't write a constructor + // but we needed to call 'super(...args)' anyway as per 14.5.14 of the ES2016 spec. + // If that's the case we can just immediately return the result of a 'super()' call. + const statements: Statement[] = []; + resumeLexicalEnvironment(); + mergeLexicalEnvironment(statements, endLexicalEnvironment()); + + if (isDerivedClass) { + // return _super !== null && _super.apply(this, arguments) || this; + statements.push(createReturn(createDefaultSuperCallOrThis())); + } + + const statementsArray = createNodeArray(statements); + setTextRange(statementsArray, node.members); + + const block = createBlock(statementsArray, /*multiLine*/ true); + setTextRange(block, node); + setEmitFlags(block, EmitFlags.NoComments); + return block; + } + /** * Transforms the body of a constructor declaration of a class. * @@ -934,82 +927,153 @@ namespace ts { * @param hasSynthesizedSuper A value indicating whether the constructor starts with a * synthesized `super` call. */ - function transformConstructorBody(constructor: ConstructorDeclaration | undefined, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments | undefined, hasSynthesizedSuper: boolean) { - const statements: Statement[] = []; - resumeLexicalEnvironment(); - - let statementOffset = -1; - if (hasSynthesizedSuper) { - // If a super call has already been synthesized, - // we're going to assume that we should just transform everything after that. - // The assumption is that no prior step in the pipeline has added any prologue directives. - statementOffset = 0; - } - else if (constructor) { - statementOffset = addStandardPrologue(statements, constructor.body!.statements, /*ensureUseStrict*/ false); - } - - if (constructor) { - addDefaultValueAssignmentsIfNeeded(statements, constructor); - addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); - if (!hasSynthesizedSuper) { - // If no super call has been synthesized, emit custom prologue directives. - statementOffset = addCustomPrologue(statements, constructor.body!.statements, statementOffset, visitor); - } - Debug.assert(statementOffset >= 0, "statementOffset not initialized correctly!"); - - } - + function transformConstructorBody(constructor: ConstructorDeclaration & { body: FunctionBody } | undefined, node: ClassDeclaration | ClassExpression, extendsClauseElement: ExpressionWithTypeArguments | undefined, hasSynthesizedSuper: boolean) { // determine whether the class is known syntactically to be a derived class (e.g. a // class that extends a value that is not syntactically known to be `null`). const isDerivedClass = !!extendsClauseElement && skipOuterExpressions(extendsClauseElement.expression).kind !== SyntaxKind.NullKeyword; - const superCaptureStatus = declareOrCaptureOrReturnThisForConstructorIfNeeded(statements, constructor, isDerivedClass, hasSynthesizedSuper, statementOffset); - // The last statement expression was replaced. Skip it. - if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture || superCaptureStatus === SuperCaptureResult.ReplaceWithReturn) { - statementOffset++; + // When the subclass does not have a constructor, we synthesize a *default* constructor using the following + // representation: + // + // ``` + // // es2015 (source) + // class C extends Base { } + // + // // es5 (transformed) + // var C = (function (_super) { + // function C() { + // return _super.apply(this, arguments) || this; + // } + // return C; + // })(Base); + // ``` + if (!constructor) return createDefaultConstructorBody(node, isDerivedClass); + + // The prologue will contain all leading standard and custom prologue statements added by this transform + const prologue: Statement[] = []; + const statements: Statement[] = []; + resumeLexicalEnvironment(); + + // If a super call has already been synthesized, + // we're going to assume that we should just transform everything after that. + // The assumption is that no prior step in the pipeline has added any prologue directives. + let statementOffset = 0; + if (!hasSynthesizedSuper) statementOffset = addStandardPrologue(prologue, constructor.body.statements, /*ensureUseStrict*/ false); + addDefaultValueAssignmentsIfNeeded(statements, constructor); + addRestParameterIfNeeded(statements, constructor, hasSynthesizedSuper); + if (!hasSynthesizedSuper) statementOffset = addCustomPrologue(statements, constructor.body.statements, statementOffset, visitor); + + // If the first statement is a call to `super()`, visit the statement directly + let superCallExpression: Expression | undefined; + if (hasSynthesizedSuper) { + superCallExpression = createDefaultSuperCallOrThis(); } - - if (constructor) { - if (superCaptureStatus === SuperCaptureResult.ReplaceSuperCapture) { - hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; + else if (isDerivedClass && statementOffset < constructor.body.statements.length) { + const firstStatement = constructor.body.statements[statementOffset]; + if (isExpressionStatement(firstStatement) && isSuperCall(firstStatement.expression)) { + superCallExpression = visitImmediateSuperCallInBody(firstStatement.expression); } - - 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. - // If there's a constructor that we can tell returns in enough places, then we *do not* want to add a return. - if (isDerivedClass - && superCaptureStatus !== SuperCaptureResult.ReplaceWithReturn - && !(constructor && isSufficientlyCoveredByReturnStatements(constructor.body!))) { - statements.push( - createReturn( - createFileLevelUniqueName("_this") - ) - ); + if (superCallExpression) { + hierarchyFacts |= HierarchyFacts.ConstructorWithCapturedSuper; + statementOffset++; // skip this statement, we will add it after visiting the rest of the body. } - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + // visit the remaining statements + addRange(statements, visitNodes(constructor.body.statements, visitor, isStatement, /*start*/ statementOffset)); - if (constructor) { - prependCaptureNewTargetIfNeeded(statements, constructor, /*copyOnWrite*/ false); + mergeLexicalEnvironment(prologue, endLexicalEnvironment()); + insertCaptureNewTargetIfNeeded(prologue, constructor, /*copyOnWrite*/ false); + + if (isDerivedClass) { + if (superCallExpression && statementOffset === constructor.body.statements.length && !(constructor.body.transformFlags & TransformFlags.ContainsLexicalThis)) { + // If the subclass constructor does *not* contain `this` and *ends* with a `super()` call, we will use the + // following representation: + // + // ``` + // // es2015 (source) + // class C extends Base { + // constructor() { + // super("foo"); + // } + // } + // + // // es5 (transformed) + // var C = (function (_super) { + // function C() { + // return _super.call(this, "foo") || this; + // } + // return C; + // })(Base); + // ``` + const superCall = cast(cast(superCallExpression, isBinaryExpression).left, isCallExpression); + const returnStatement = createReturn(superCallExpression); + setCommentRange(returnStatement, getCommentRange(superCall)); + setEmitFlags(superCall, EmitFlags.NoComments); + statements.push(returnStatement); + } + else { + // Otherwise, we will use the following transformed representation for calls to `super()` in a constructor: + // + // ``` + // // es2015 (source) + // class C extends Base { + // constructor() { + // super("foo"); + // this.x = 1; + // } + // } + // + // // es5 (transformed) + // var C = (function (_super) { + // function C() { + // var _this = _super.call(this, "foo") || this; + // _this.x = 1; + // return _this; + // } + // return C; + // })(Base); + // ``` + + // Since the `super()` call was the first statement, we insert the `this` capturing call to + // `super()` at the top of the list of `statements` (after any pre-existing custom prologues). + insertCaptureThisForNode(statements, constructor, superCallExpression || createActualThis()); + + if (!isSufficientlyCoveredByReturnStatements(constructor.body)) { + statements.push(createReturn(createFileLevelUniqueName("_this"))); + } + } + } + else { + // If a class is not derived from a base class or does not have a call to `super()`, `this` is only + // captured when necessitated by an arrow function capturing the lexical `this`: + // + // ``` + // // es2015 + // class C {} + // + // // es5 + // var C = (function () { + // function C() { + // } + // return C; + // })(); + // ``` + insertCaptureThisForNodeIfNeeded(prologue, constructor); } const block = createBlock( setTextRange( createNodeArray( - statements + concatenate(prologue, statements) ), - /*location*/ constructor ? constructor.body!.statements : node.members + /*location*/ constructor.body.statements ), /*multiLine*/ true ); - setTextRange(block, constructor ? constructor.body : node); - if (!constructor) { - setEmitFlags(block, EmitFlags.NoComments); - } + setTextRange(block, constructor.body); return block; } @@ -1043,104 +1107,6 @@ namespace ts { return false; } - /** - * Declares a `_this` variable for derived classes and for when arrow functions capture `this`. - * - * @returns The new statement offset into the `statements` array. - */ - function declareOrCaptureOrReturnThisForConstructorIfNeeded( - statements: Statement[], - ctor: ConstructorDeclaration | undefined, - isDerivedClass: boolean, - hasSynthesizedSuper: boolean, - statementOffset: number) { - // If this isn't a derived class, just capture 'this' for arrow functions if necessary. - if (!isDerivedClass) { - if (ctor) { - addCaptureThisForNodeIfNeeded(statements, ctor); - } - return SuperCaptureResult.NoReplacement; - } - - // We must be here because the user didn't write a constructor - // but we needed to call 'super(...args)' anyway as per 14.5.14 of the ES2016 spec. - // If that's the case we can just immediately return the result of a 'super()' call. - if (!ctor) { - statements.push(createReturn(createDefaultSuperCallOrThis())); - return SuperCaptureResult.ReplaceWithReturn; - } - - // The constructor exists, but it and the 'super()' call it contains were generated - // for something like property initializers. - // Create a captured '_this' variable and assume it will subsequently be used. - if (hasSynthesizedSuper) { - captureThisForNode(statements, ctor, createDefaultSuperCallOrThis()); - enableSubstitutionsForCapturedThis(); - return SuperCaptureResult.ReplaceSuperCapture; - } - - // Most of the time, a 'super' call will be the first real statement in a constructor body. - // In these cases, we'd like to transform these into a *single* statement instead of a declaration - // followed by an assignment statement for '_this'. For instance, if we emitted without an initializer, - // we'd get: - // - // var _this; - // _this = _super.call(...) || this; - // - // instead of - // - // var _this = _super.call(...) || this; - // - // Additionally, if the 'super()' call is the last statement, we should just avoid capturing - // entirely and immediately return the result like so: - // - // return _super.call(...) || this; - // - let firstStatement: Statement | undefined; - let superCallExpression: Expression | undefined; - - const ctorStatements = ctor.body!.statements; - if (statementOffset < ctorStatements.length) { - firstStatement = ctorStatements[statementOffset]; - - if (firstStatement.kind === SyntaxKind.ExpressionStatement && isSuperCall((firstStatement as ExpressionStatement).expression)) { - superCallExpression = visitImmediateSuperCallInBody((firstStatement as ExpressionStatement).expression as CallExpression); - } - } - - // Return the result if we have an immediate super() call on the last statement, - // but only if the constructor itself doesn't use 'this' elsewhere. - if (superCallExpression - && statementOffset === ctorStatements.length - 1 - && !(ctor.transformFlags & (TransformFlags.ContainsLexicalThis | TransformFlags.ContainsCapturedLexicalThis))) { - const returnStatement = createReturn(superCallExpression); - - if (superCallExpression.kind !== SyntaxKind.BinaryExpression - || (superCallExpression as BinaryExpression).left.kind !== SyntaxKind.CallExpression) { - Debug.fail("Assumed generated super call would have form 'super.call(...) || this'."); - } - - // Shift comments from the original super call to the return statement. - setCommentRange(returnStatement, getCommentRange( - setEmitFlags( - (superCallExpression as BinaryExpression).left, - EmitFlags.NoComments))); - - statements.push(returnStatement); - return SuperCaptureResult.ReplaceWithReturn; - } - - // Perform the capture. - captureThisForNode(statements, ctor, superCallExpression || createActualThis()); - - // If we're actually replacing the original statement, we need to signal this to the caller. - if (superCallExpression) { - return SuperCaptureResult.ReplaceSuperCapture; - } - - return SuperCaptureResult.NoReplacement; - } - function createActualThis() { return setEmitFlags(createThis(), EmitFlags.NoSubstitution); } @@ -1214,14 +1180,9 @@ 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 hasDefaultValueOrBindingPattern(node: ParameterDeclaration) { + return node.initializer !== undefined + || isBindingPattern(node.name); } /** @@ -1231,11 +1192,12 @@ namespace ts { * @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; + function addDefaultValueAssignmentsIfNeeded(statements: Statement[], node: FunctionLikeDeclaration): boolean { + if (!some(node.parameters, hasDefaultValueOrBindingPattern)) { + return false; } + let added = false; for (const parameter of node.parameters) { const { name, initializer, dotDotDotToken } = parameter; @@ -1246,12 +1208,14 @@ namespace ts { } if (isBindingPattern(name)) { - addDefaultValueAssignmentForBindingPattern(statements, parameter, name, initializer); + added = insertDefaultValueAssignmentForBindingPattern(statements, parameter, name, initializer) || added; } else if (initializer) { - addDefaultValueAssignmentForInitializer(statements, parameter, name, initializer); + insertDefaultValueAssignmentForInitializer(statements, parameter, name, initializer); + added = true; } } + return added; } /** @@ -1262,14 +1226,13 @@ namespace ts { * @param name The name of the parameter. * @param initializer The initializer for the parameter. */ - function addDefaultValueAssignmentForBindingPattern(statements: Statement[], parameter: ParameterDeclaration, name: BindingPattern, initializer: Expression | undefined): void { - const temp = getGeneratedNameForNode(parameter); - + function insertDefaultValueAssignmentForBindingPattern(statements: Statement[], parameter: ParameterDeclaration, name: BindingPattern, initializer: Expression | undefined): boolean { // In cases where a binding pattern is simply '[]' or '{}', // 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) { - statements.push( + insertStatementAfterCustomPrologue( + statements, setEmitFlags( createVariableStatement( /*modifiers*/ undefined, @@ -1279,27 +1242,31 @@ namespace ts { visitor, context, FlattenLevel.All, - temp + getGeneratedNameForNode(parameter) ) ) ), EmitFlags.CustomPrologue ) ); + return true; } else if (initializer) { - statements.push( + insertStatementAfterCustomPrologue( + statements, setEmitFlags( createExpressionStatement( createAssignment( - temp, + getGeneratedNameForNode(parameter), visitNode(initializer, visitor, isExpression) ) ), EmitFlags.CustomPrologue ) ); + return true; } + return false; } /** @@ -1310,7 +1277,7 @@ namespace ts { * @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 { + function insertDefaultValueAssignmentForInitializer(statements: Statement[], parameter: ParameterDeclaration, name: Identifier, initializer: Expression): void { initializer = visitNode(initializer, visitor, isExpression); const statement = createIf( createTypeCheck(getSynthesizedClone(name), "undefined"), @@ -1339,7 +1306,7 @@ namespace ts { startOnNewLine(statement); setTextRange(statement, parameter); setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue | EmitFlags.NoComments); - statements.push(statement); + insertStatementAfterCustomPrologue(statements, statement); } /** @@ -1363,10 +1330,11 @@ namespace ts { * part of a constructor declaration with a * synthesized call to `super` */ - function addRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): void { + function addRestParameterIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, inConstructorWithSynthesizedSuper: boolean): boolean { + const prologueStatements: Statement[] = []; const parameter = lastOrUndefined(node.parameters); if (!shouldAddRestParameter(parameter, inConstructorWithSynthesizedSuper)) { - return; + return false; } // `declarationName` is the name of the local declaration for the parameter. @@ -1379,7 +1347,7 @@ namespace ts { const temp = createLoopVariable(); // var param = []; - statements.push( + prologueStatements.push( setEmitFlags( setTextRange( createVariableStatement( @@ -1438,11 +1406,11 @@ namespace ts { setEmitFlags(forStatement, EmitFlags.CustomPrologue); startOnNewLine(forStatement); - statements.push(forStatement); + prologueStatements.push(forStatement); if (parameter.name.kind !== SyntaxKind.Identifier) { // do the actual destructuring of the rest parameter if necessary - statements.push( + prologueStatements.push( setEmitFlags( setTextRange( createVariableStatement( @@ -1457,21 +1425,27 @@ namespace ts { ) ); } + + insertStatementsAfterCustomPrologue(statements, prologueStatements); + return true; } /** * Adds a statement to capture the `this` of a function declaration if it is needed. + * NOTE: This must be executed *after* the subtree has been visited. * * @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) { - captureThisForNode(statements, node, createThis()); + function insertCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): boolean { + if (hierarchyFacts & HierarchyFacts.CapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) { + insertCaptureThisForNode(statements, node, createThis()); + return true; } + return false; } - function captureThisForNode(statements: Statement[], node: Node, initializer: Expression | undefined): void { + function insertCaptureThisForNode(statements: Statement[], node: Node, initializer: Expression | undefined): void { enableSubstitutionsForCapturedThis(); const captureThisStatement = createVariableStatement( /*modifiers*/ undefined, @@ -1485,10 +1459,10 @@ namespace ts { ); setEmitFlags(captureThisStatement, EmitFlags.NoComments | EmitFlags.CustomPrologue); setSourceMapRange(captureThisStatement, node); - statements.push(captureThisStatement); + insertStatementAfterCustomPrologue(statements, captureThisStatement); } - function prependCaptureNewTargetIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, copyOnWrite: boolean): Statement[] { + function insertCaptureNewTargetIfNeeded(statements: Statement[], node: FunctionLikeDeclaration, copyOnWrite: boolean): Statement[] { if (hierarchyFacts & HierarchyFacts.NewTarget) { let newTarget: Expression; switch (node.kind) { @@ -1548,11 +1522,13 @@ namespace ts { ]) ); + setEmitFlags(captureNewTargetStatement, EmitFlags.NoComments | EmitFlags.CustomPrologue); + if (copyOnWrite) { - return [captureNewTargetStatement, ...statements]; + statements = statements.slice(); } - statements.unshift(captureNewTargetStatement); + insertStatementAfterCustomPrologue(statements, captureNewTargetStatement); } return statements; @@ -1612,7 +1588,6 @@ 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); @@ -1634,8 +1609,6 @@ namespace ts { // No source map should be emitted for this statement to align with the // old emitter. setEmitFlags(statement, EmitFlags.NoSourceMap); - - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, hierarchyFacts & HierarchyFacts.PropagateNewTargetMask ? HierarchyFacts.NewTarget : HierarchyFacts.None); return statement; } @@ -1662,8 +1635,6 @@ 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); @@ -1711,7 +1682,6 @@ namespace ts { startOnNewLine(call); } - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, hierarchyFacts & HierarchyFacts.PropagateNewTargetMask ? HierarchyFacts.NewTarget : HierarchyFacts.None); return call; } @@ -1722,8 +1692,9 @@ namespace ts { */ function visitArrowFunction(node: ArrowFunction) { if (node.transformFlags & TransformFlags.ContainsLexicalThis) { - enableSubstitutionsForCapturedThis(); + hierarchyFacts |= HierarchyFacts.CapturedLexicalThis; } + const savedConvertedLoopState = convertedLoopState; convertedLoopState = undefined; const ancestorFacts = enterSubtree(HierarchyFacts.ArrowFunctionExcludes, HierarchyFacts.ArrowFunctionIncludes); @@ -1739,7 +1710,14 @@ namespace ts { setTextRange(func, node); setOriginalNode(func, node); setEmitFlags(func, EmitFlags.CapturesThis); - exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); + + if (hierarchyFacts & HierarchyFacts.CapturedLexicalThis) { + enableSubstitutionsForCapturedThis(); + } + + // If an arrow function contains + exitSubtree(ancestorFacts, HierarchyFacts.ArrowFunctionSubtreeExcludes, HierarchyFacts.None); + convertedLoopState = savedConvertedLoopState; return func; } @@ -1757,14 +1735,12 @@ namespace ts { convertedLoopState = undefined; const parameters = visitParameterList(node.parameters, visitor, context); - const body = node.transformFlags & TransformFlags.ES2015 - ? transformFunctionBody(node) - : visitFunctionBodyDownLevel(node); + const body = transformFunctionBody(node); const name = hierarchyFacts & HierarchyFacts.NewTarget ? getLocalName(node) : node.name; - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); + exitSubtree(ancestorFacts, HierarchyFacts.FunctionSubtreeExcludes, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updateFunctionExpression( node, @@ -1788,14 +1764,12 @@ namespace ts { convertedLoopState = undefined; const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); const parameters = visitParameterList(node.parameters, visitor, context); - const body = node.transformFlags & TransformFlags.ES2015 - ? transformFunctionBody(node) - : visitFunctionBodyDownLevel(node); + const body = transformFunctionBody(node); const name = hierarchyFacts & HierarchyFacts.NewTarget ? getLocalName(node) : node.name; - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); + exitSubtree(ancestorFacts, HierarchyFacts.FunctionSubtreeExcludes, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updateFunctionDeclaration( node, @@ -1829,7 +1803,7 @@ namespace ts { name = getGeneratedNameForNode(node); } - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); + exitSubtree(ancestorFacts, HierarchyFacts.FunctionSubtreeExcludes, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return setOriginalNode( setTextRange( @@ -1859,7 +1833,7 @@ namespace ts { let statementsLocation: TextRange; let closeBraceLocation: TextRange | undefined; - const leadingStatements: Statement[] = []; + const prologue: Statement[] = []; const statements: Statement[] = []; const body = node.body!; let statementOffset: number | undefined; @@ -1868,16 +1842,15 @@ namespace ts { if (isBlock(body)) { // ensureUseStrict is false because no new prologue-directive should be added. // addStandardPrologue will put already-existing directives at the beginning of the target statement-array - statementOffset = addStandardPrologue(leadingStatements, body.statements, /*ensureUseStrict*/ false); + statementOffset = addStandardPrologue(prologue, body.statements, /*ensureUseStrict*/ false); } - addCaptureThisForNodeIfNeeded(leadingStatements, node); - addDefaultValueAssignmentsIfNeeded(leadingStatements, node); - addRestParameterIfNeeded(leadingStatements, node, /*inConstructorWithSynthesizedSuper*/ false); + multiLine = addDefaultValueAssignmentsIfNeeded(statements, node) || multiLine; + multiLine = addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false) || multiLine; if (isBlock(body)) { // addCustomPrologue puts already-existing directives at the beginning of the target statement-array - statementOffset = addCustomPrologue(leadingStatements, body.statements, statementOffset, visitor); + statementOffset = addCustomPrologue(statements, body.statements, statementOffset, visitor); statementsLocation = body.statements; addRange(statements, visitNodes(body.statements, visitor, isStatement, statementOffset)); @@ -1918,16 +1891,22 @@ namespace ts { closeBraceLocation = body; } - const lexicalEnvironment = context.endLexicalEnvironment(); - addStatementsAfterPrologue(statements, lexicalEnvironment); - prependCaptureNewTargetIfNeeded(statements, node, /*copyOnWrite*/ false); + mergeLexicalEnvironment(prologue, endLexicalEnvironment()); + insertCaptureNewTargetIfNeeded(prologue, node, /*copyOnWrite*/ false); + insertCaptureThisForNodeIfNeeded(prologue, node); // If we added any final generated statements, this must be a multi-line block - if (some(leadingStatements) || some(lexicalEnvironment)) { + if (some(prologue)) { multiLine = true; } - const block = createBlock(setTextRange(createNodeArray([...leadingStatements, ...statements]), statementsLocation), multiLine); + statements.unshift(...prologue); + if (isBlock(body) && arrayIsEqualTo(statements, body.statements)) { + // no changes were made, preserve the tree + return body; + } + + const block = createBlock(setTextRange(createNodeArray(statements), statementsLocation), multiLine); setTextRange(block, node.body); if (!multiLine && singleLine) { setEmitFlags(block, EmitFlags.SingleLine); @@ -1941,19 +1920,6 @@ namespace ts { return block; } - function visitFunctionBodyDownLevel(node: FunctionDeclaration | FunctionExpression | AccessorDeclaration) { - const updated = visitFunctionBody(node.body, functionBodyVisitor, context)!; - return updateBlock( - updated, - setTextRange( - createNodeArray( - prependCaptureNewTargetIfNeeded(updated.statements as MutableNodeArray, node, /*copyOnWrite*/ true) - ), - /*location*/ updated.statements - ) - ); - } - function visitBlock(node: Block, isFunctionBody: boolean): Block { if (isFunctionBody) { // A function body is not a block scope. @@ -2074,7 +2040,7 @@ namespace ts { * @param node A VariableDeclarationList node. */ function visitVariableDeclarationList(node: VariableDeclarationList): VariableDeclarationList { - if (node.transformFlags & TransformFlags.ES2015) { + if (node.flags & NodeFlags.BlockScoped || node.transformFlags & TransformFlags.ContainsBindingPattern) { if (node.flags & NodeFlags.BlockScoped) { enableSubstitutionsForBlockScopedBindings(); } @@ -3104,7 +3070,7 @@ namespace ts { } copyOutParameters(currentState.loopOutParameters, LoopOutParameterFlags.Body, CopyDirection.ToOutParameter, statements); - addStatementsAfterPrologue(statements, lexicalEnvironment); + insertStatementsAfterStandardPrologue(statements, lexicalEnvironment); const loopBody = createBlock(statements, /*multiLine*/ true); if (isBlock(statement)) setOriginalNode(loopBody, statement); @@ -3426,7 +3392,6 @@ 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, @@ -3438,7 +3403,6 @@ namespace ts { if (startsOnNewLine) { startOnNewLine(expression); } - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, hierarchyFacts & HierarchyFacts.PropagateNewTargetMask ? HierarchyFacts.NewTarget : HierarchyFacts.None); return expression; } @@ -3509,16 +3473,14 @@ namespace ts { const ancestorFacts = enterSubtree(HierarchyFacts.FunctionExcludes, HierarchyFacts.FunctionIncludes); let updated: AccessorDeclaration; const parameters = visitParameterList(node.parameters, visitor, context); - const body = node.transformFlags & (TransformFlags.ContainsCapturedLexicalThis | TransformFlags.ContainsES2015) - ? transformFunctionBody(node) - : visitFunctionBodyDownLevel(node); + const body = transformFunctionBody(node); if (node.kind === SyntaxKind.GetAccessor) { updated = updateGetAccessor(node, node.decorators, node.modifiers, node.name, parameters, node.type, body); } else { updated = updateSetAccessor(node, node.decorators, node.modifiers, node.name, parameters, body); } - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, HierarchyFacts.None); + exitSubtree(ancestorFacts, HierarchyFacts.FunctionSubtreeExcludes, HierarchyFacts.None); convertedLoopState = savedConvertedLoopState; return updated; } @@ -3539,10 +3501,7 @@ namespace ts { } function visitComputedPropertyName(node: ComputedPropertyName) { - const ancestorFacts = enterSubtree(HierarchyFacts.ComputedPropertyNameExcludes, HierarchyFacts.ComputedPropertyNameIncludes); - const updated = visitEachChild(node, visitor, context); - exitSubtree(ancestorFacts, HierarchyFacts.PropagateNewTargetMask, hierarchyFacts & HierarchyFacts.PropagateNewTargetMask ? HierarchyFacts.NewTargetInComputedPropertyName : HierarchyFacts.None); - return updated; + return visitEachChild(node, visitor, context); } /** @@ -3561,7 +3520,7 @@ namespace ts { * @param node An ArrayLiteralExpression node. */ function visitArrayLiteralExpression(node: ArrayLiteralExpression): Expression { - if (node.transformFlags & TransformFlags.ES2015) { + if (some(node.elements, isSpreadElement)) { // We are here because we contain a SpreadElementExpression. return transformAndSpreadElements(node.elements, /*needsUniqueCopy*/ true, !!node.multiLine, /*hasTrailingComma*/ !!node.elements.hasTrailingComma); } @@ -3578,7 +3537,10 @@ namespace ts { return visitTypeScriptClassWrapper(node); } - if (node.transformFlags & TransformFlags.ES2015) { + const expression = skipOuterExpressions(node.expression); + if (expression.kind === SyntaxKind.SuperKeyword || + isSuperProperty(expression) || + some(node.arguments, isSpreadElement)) { return visitCallExpressionWithPotentialCapturedThisAssignment(node, /*assignToCapturedThis*/ true); } @@ -3774,7 +3736,7 @@ namespace ts { resultingCall = createFunctionApply( visitNode(target, callExpressionVisitor, isExpression), - visitNode(thisArg, visitor, isExpression), + node.expression.kind === SyntaxKind.SuperKeyword ? thisArg : visitNode(thisArg, visitor, isExpression), transformAndSpreadElements(node.arguments, /*needsUniqueCopy*/ false, /*multiLine*/ false, /*hasTrailingComma*/ false) ); } @@ -3790,19 +3752,17 @@ namespace ts { // _super.prototype.m.call(this, a) resultingCall = createFunctionCall( visitNode(target, callExpressionVisitor, isExpression), - visitNode(thisArg, visitor, isExpression), + node.expression.kind === SyntaxKind.SuperKeyword ? thisArg : visitNode(thisArg, visitor, isExpression), visitNodes(node.arguments, visitor, isExpression), /*location*/ node ); } if (node.expression.kind === SyntaxKind.SuperKeyword) { - const actualThis = createThis(); - setEmitFlags(actualThis, EmitFlags.NoSubstitution); const initializer = createLogicalOr( resultingCall, - actualThis + createActualThis() ); resultingCall = assignToCapturedThis ? createAssignment(createFileLevelUniqueName("_this"), initializer) @@ -3820,7 +3780,7 @@ namespace ts { * @param node A NewExpression node. */ function visitNewExpression(node: NewExpression): LeftHandSideExpression { - if (node.transformFlags & TransformFlags.ContainsRestOrSpread) { + if (some(node.arguments, isSpreadElement)) { // We are here because we contain a SpreadElementExpression. // [source] // new C(...a) @@ -4132,12 +4092,7 @@ namespace ts { function visitMetaProperty(node: MetaProperty) { if (node.keywordToken === SyntaxKind.NewKeyword && node.name.escapedText === "target") { - if (hierarchyFacts & HierarchyFacts.ComputedPropertyName) { - hierarchyFacts |= HierarchyFacts.NewTargetInComputedPropertyName; - } - else { - hierarchyFacts |= HierarchyFacts.NewTarget; - } + hierarchyFacts |= HierarchyFacts.NewTarget; return createFileLevelUniqueName("_newTarget"); } return node; diff --git a/src/compiler/transformers/es2017.ts b/src/compiler/transformers/es2017.ts index b509c6dd003..c1e586085cf 100644 --- a/src/compiler/transformers/es2017.ts +++ b/src/compiler/transformers/es2017.ts @@ -438,7 +438,7 @@ namespace ts { ) ); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); // Minor optimization, emit `_super` helper to capture `super` access in an arrow. // This step isn't needed if we eventually transform this to ES5. @@ -448,7 +448,7 @@ namespace ts { enableSubstitutionForAsyncMethodsWithSuper(); const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties); substitutedSuperAccessors[getNodeId(variableStatement)] = true; - addStatementsAfterPrologue(statements, [variableStatement]); + insertStatementsAfterStandardPrologue(statements, [variableStatement]); } const block = createBlock(statements, /*multiLine*/ true); diff --git a/src/compiler/transformers/es2018.ts b/src/compiler/transformers/es2018.ts index 3087249373a..77fa79c6c45 100644 --- a/src/compiler/transformers/es2018.ts +++ b/src/compiler/transformers/es2018.ts @@ -689,12 +689,12 @@ namespace ts { enableSubstitutionForAsyncMethodsWithSuper(); const variableStatement = createSuperAccessVariableStatement(resolver, node, capturedSuperProperties); substitutedSuperAccessors[getNodeId(variableStatement)] = true; - addStatementsAfterPrologue(statements, [variableStatement]); + insertStatementsAfterStandardPrologue(statements, [variableStatement]); } statements.push(returnStatement); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const block = updateBlock(node.body!, statements); if (emitSuperHelpers && hasSuperElementAccess) { @@ -726,7 +726,7 @@ namespace ts { const leadingStatements = endLexicalEnvironment(); if (statementOffset > 0 || some(statements) || some(leadingStatements)) { const block = convertToFunctionBody(body, /*multiLine*/ true); - addStatementsAfterPrologue(statements, leadingStatements); + insertStatementsAfterStandardPrologue(statements, leadingStatements); addRange(statements, block.statements.slice(statementOffset)); return updateBlock(block, setTextRange(createNodeArray(statements), block.statements)); } diff --git a/src/compiler/transformers/generators.ts b/src/compiler/transformers/generators.ts index 5778b47a4f8..97cc1d5b062 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -316,7 +316,7 @@ namespace ts { else if (inGeneratorFunctionBody) { return visitJavaScriptInGeneratorFunctionBody(node); } - else if (transformFlags & TransformFlags.Generator) { + else if (isFunctionLikeDeclaration(node) && node.asteriskToken) { return visitGenerator(node); } else if (transformFlags & TransformFlags.ContainsGenerator) { @@ -587,7 +587,7 @@ namespace ts { transformAndEmitStatements(body.statements, statementOffset); const buildResult = build(); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); statements.push(createReturn(buildResult)); // Restore previous generator state diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 1b3a623c32d..621012ebc3b 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -100,7 +100,7 @@ namespace ts { append(statements, visitNode(currentModuleInfo.externalHelpersImportDeclaration, sourceElementVisitor, isStatement)); addRange(statements, visitNodes(node.statements, sourceElementVisitor, isStatement, statementOffset)); addExportEqualsIfNeeded(statements, /*emitAsReturn*/ false); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const updated = updateSourceFileNode(node, setTextRange(createNodeArray(statements), node.statements)); if (currentModuleInfo.hasExportStarsToExportValues && !compilerOptions.importHelpers) { @@ -432,7 +432,7 @@ namespace ts { // End the lexical environment for the module body // and merge any new lexical declarations. - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const body = createBlock(statements, /*multiLine*/ true); if (currentModuleInfo.hasExportStarsToExportValues && !compilerOptions.importHelpers) { @@ -537,8 +537,8 @@ namespace ts { if (isImportCall(node)) { return visitImportCallExpression(node); } - else if (node.transformFlags & TransformFlags.DestructuringAssignment && isBinaryExpression(node)) { - return visitDestructuringAssignment(node as DestructuringAssignment); + else if (isDestructuringAssignment(node)) { + return visitDestructuringAssignment(node); } else { return visitEachChild(node, moduleExpressionElementVisitor, context); diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 82815528c21..ae303440bc1 100644 --- a/src/compiler/transformers/module/system.ts +++ b/src/compiler/transformers/module/system.ts @@ -257,7 +257,7 @@ namespace ts { // We emit hoisted variables early to align roughly with our previous emit output. // Two key differences in this approach are: // - Temporary variables will appear at the top rather than at the bottom of the file - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); const exportStarFunction = addExportStarIfNeeded(statements)!; // TODO: GH#18217 const moduleObject = createObjectLiteral([ @@ -1463,9 +1463,8 @@ namespace ts { * @param node The node to visit. */ function destructuringAndImportCallVisitor(node: Node): VisitResult { - if (node.transformFlags & TransformFlags.DestructuringAssignment - && node.kind === SyntaxKind.BinaryExpression) { - return visitDestructuringAssignment(node); + if (isDestructuringAssignment(node)) { + return visitDestructuringAssignment(node); } else if (isImportCall(node)) { return visitImportCallExpression(node); diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 473f3ed0d14..5774ff8d0fc 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -208,15 +208,9 @@ namespace ts { * @param node The node to visit. */ function visitorWorker(node: Node): VisitResult { - if (node.transformFlags & TransformFlags.TypeScript) { - // This node is explicitly marked as TypeScript, so we should transform the node. + if (node.transformFlags & TransformFlags.ContainsTypeScript) { return visitTypeScript(node); } - else if (node.transformFlags & TransformFlags.ContainsTypeScript) { - // This node contains TypeScript, so we should visit its children. - return visitEachChild(node, visitor, context); - } - return node; } @@ -296,15 +290,9 @@ namespace ts { (node).moduleReference.kind === SyntaxKind.ExternalModuleReference)) { // do not emit ES6 imports and exports since they are illegal inside a namespace return undefined; - } - else if (node.transformFlags & TransformFlags.TypeScript || hasModifier(node, ModifierFlags.Export)) { - // This node is explicitly marked as TypeScript, or is exported at the namespace - // level, so we should transform the node. - return visitTypeScript(node); } - else if (node.transformFlags & TransformFlags.ContainsTypeScript) { - // This node contains TypeScript, so we should visit its children. - return visitEachChild(node, visitor, context); + else if (node.transformFlags & TransformFlags.ContainsTypeScript || hasModifier(node, ModifierFlags.Export)) { + return visitTypeScript(node); } return node; @@ -365,7 +353,7 @@ namespace ts { * @param node The node to visit. */ function visitTypeScript(node: Node): VisitResult { - if (hasModifier(node, ModifierFlags.Ambient) && isStatement(node)) { + if (isStatement(node) && hasModifier(node, ModifierFlags.Ambient)) { // TypeScript ambient declarations are elided, but some comments may be preserved. // See the implementation of `getLeadingComments` in comments.ts for more details. return createNotEmittedStatement(node); @@ -443,7 +431,7 @@ namespace ts { return createNotEmittedStatement(node); case SyntaxKind.ClassDeclaration: - // This is a class declaration with TypeScript syntax extensions. + // This may be a class declaration with TypeScript syntax extensions. // // TypeScript class syntax extensions include: // - decorators @@ -455,7 +443,7 @@ namespace ts { return visitClassDeclaration(node); case SyntaxKind.ClassExpression: - // This is a class expression with TypeScript syntax extensions. + // This may be a class expression with TypeScript syntax extensions. // // TypeScript class syntax extensions include: // - decorators @@ -467,7 +455,7 @@ namespace ts { return visitClassExpression(node); case SyntaxKind.HeritageClause: - // This is a heritage clause with TypeScript syntax extensions. + // This may be a heritage clause with TypeScript syntax extensions. // // TypeScript heritage clause extensions include: // - `implements` clause @@ -503,7 +491,7 @@ namespace ts { return visitArrowFunction(node); case SyntaxKind.Parameter: - // This is a parameter declaration with TypeScript syntax extensions. + // This may be a parameter declaration with TypeScript syntax extensions. // // TypeScript parameter declaration syntax extensions include: // - decorators @@ -556,7 +544,8 @@ namespace ts { return visitImportEqualsDeclaration(node); default: - return Debug.failBadSyntaxKind(node); + // node contains some other TypeScript syntax + return visitEachChild(node, visitor, context); } } @@ -607,18 +596,22 @@ namespace ts { return facts; } - /** - * Transforms a class declaration with TypeScript syntax into compatible ES6. - * - * This function will only be called when one of the following conditions are met: - * - The class has decorators. - * - The class has property declarations with initializers. - * - The class contains a constructor that contains parameters with accessibility modifiers. - * - The class is an export in a TypeScript namespace. - * - * @param node The node to transform. - */ + function hasTypeScriptClassSyntax(node: Node) { + return !!(node.transformFlags & TransformFlags.ContainsTypeScriptClassSyntax); + } + + function isClassLikeDeclarationWithTypeScriptSyntax(node: ClassLikeDeclaration) { + return some(node.decorators) + || some(node.typeParameters) + || some(node.heritageClauses, hasTypeScriptClassSyntax) + || some(node.members, hasTypeScriptClassSyntax); + } + function visitClassDeclaration(node: ClassDeclaration): VisitResult { + if (!isClassLikeDeclarationWithTypeScriptSyntax(node) && !(currentNamespace && hasModifier(node, ModifierFlags.Export))) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; pendingExpressions = undefined; @@ -682,7 +675,7 @@ namespace ts { setEmitFlags(statement, EmitFlags.NoComments | EmitFlags.NoTokenSourceMaps); statements.push(statement); - addStatementsAfterPrologue(statements, context.endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, context.endLexicalEnvironment()); const iife = createImmediatelyInvokedArrowFunction(statements); setEmitFlags(iife, EmitFlags.TypeScriptClassWrapper); @@ -890,16 +883,11 @@ namespace ts { return statement; } - /** - * Transforms a class expression with TypeScript syntax into compatible ES6. - * - * This function will only be called when one of the following conditions are met: - * - The class has property declarations with initializers. - * - The class contains a constructor that contains parameters with accessibility modifiers. - * - * @param node The node to transform. - */ function visitClassExpression(node: ClassExpression): Expression { + if (!isClassLikeDeclarationWithTypeScriptSyntax(node)) { + return visitEachChild(node, visitor, context); + } + const savedPendingExpressions = pendingExpressions; pendingExpressions = undefined; @@ -2237,18 +2225,11 @@ namespace ts { * @param node The HeritageClause to transform. */ function visitHeritageClause(node: HeritageClause): HeritageClause | undefined { - if (node.token === SyntaxKind.ExtendsKeyword) { - const types = visitNodes(node.types, visitor, isExpressionWithTypeArguments, 0, 1); - return setTextRange( - createHeritageClause( - SyntaxKind.ExtendsKeyword, - types - ), - node - ); + if (node.token === SyntaxKind.ImplementsKeyword) { + // implements clauses are elided + return undefined; } - - return undefined; + return visitEachChild(node, visitor, context); } /** @@ -2299,16 +2280,6 @@ namespace ts { ); } - /** - * Visits a method declaration of a class. - * - * This function will be called when one of the following conditions are met: - * - The node is an overload - * - The node is marked as abstract, public, private, protected, or readonly - * - The node has a computed property name - * - * @param node The method node. - */ function visitMethodDeclaration(node: MethodDeclaration) { if (!shouldEmitFunctionLikeDeclaration(node)) { return undefined; @@ -2344,15 +2315,6 @@ namespace ts { return !(nodeIsMissing(node.body) && hasModifier(node, ModifierFlags.Abstract)); } - /** - * Visits a get accessor declaration of a class. - * - * This function will be called when one of the following conditions are met: - * - The node is marked as abstract, public, private, or protected - * - The node has a computed property name - * - * @param node The get accessor node. - */ function visitGetAccessor(node: GetAccessorDeclaration) { if (!shouldEmitAccessorDeclaration(node)) { return undefined; @@ -2375,15 +2337,6 @@ namespace ts { return updated; } - /** - * Visits a set accessor declaration of a class. - * - * This function will be called when one of the following conditions are met: - * - The node is marked as abstract, public, private, or protected - * - The node has a computed property name - * - * @param node The set accessor node. - */ function visitSetAccessor(node: SetAccessorDeclaration) { if (!shouldEmitAccessorDeclaration(node)) { return undefined; @@ -2405,16 +2358,6 @@ namespace ts { return updated; } - /** - * Visits a function declaration. - * - * This function will be called when one of the following conditions are met: - * - The node is an overload - * - The node is exported from a TypeScript namespace - * - The node has decorators - * - * @param node The function node. - */ function visitFunctionDeclaration(node: FunctionDeclaration): VisitResult { if (!shouldEmitFunctionLikeDeclaration(node)) { return createNotEmittedStatement(node); @@ -2438,14 +2381,6 @@ namespace ts { return updated; } - /** - * Visits a function expression node. - * - * This function will be called when one of the following conditions are met: - * - The node has type annotations - * - * @param node The function expression node. - */ function visitFunctionExpression(node: FunctionExpression): Expression { if (!shouldEmitFunctionLikeDeclaration(node)) { return createOmittedExpression(); @@ -2463,11 +2398,6 @@ namespace ts { return updated; } - /** - * @remarks - * This function will be called when one of the following conditions are met: - * - The node has type annotations - */ function visitArrowFunction(node: ArrowFunction) { const updated = updateArrowFunction( node, @@ -2481,22 +2411,12 @@ namespace ts { return updated; } - /** - * Visits a parameter declaration node. - * - * This function will be called when one of the following conditions are met: - * - The node has an accessibility modifier. - * - The node has a questionToken. - * - The node's kind is ThisKeyword. - * - * @param node The parameter declaration node. - */ function visitParameter(node: ParameterDeclaration) { if (parameterIsThisKeyword(node)) { return undefined; } - - const parameter = createParameter( + const updated = updateParameter( + node, /*decorators*/ undefined, /*modifiers*/ undefined, node.dotDotDotToken, @@ -2505,24 +2425,17 @@ namespace ts { /*type*/ undefined, visitNode(node.initializer, visitor, isExpression) ); - - // While we emit the source map for the node after skipping decorators and modifiers, - // we need to emit the comments for the original range. - setOriginalNode(parameter, node); - setTextRange(parameter, moveRangePastModifiers(node)); - setCommentRange(parameter, node); - setSourceMapRange(parameter, moveRangePastModifiers(node)); - setEmitFlags(parameter.name, EmitFlags.NoTrailingSourceMap); - - return parameter; + if (updated !== node) { + // While we emit the source map for the node after skipping decorators and modifiers, + // we need to emit the comments for the original range. + setCommentRange(updated, node); + setTextRange(updated, moveRangePastModifiers(node)); + setSourceMapRange(updated, moveRangePastModifiers(node)); + setEmitFlags(updated.name, EmitFlags.NoTrailingSourceMap); + } + return updated; } - /** - * Visits a variable statement in a namespace. - * - * This function will be called when one of the following conditions are met: - * - The node is exported from a TypeScript namespace. - */ function visitVariableStatement(node: VariableStatement): Statement | undefined { if (isExportOfNamespace(node)) { const variables = getInitializedVariables(node.declarationList); @@ -2576,12 +2489,6 @@ namespace ts { visitNode(node.initializer, visitor, isExpression)); } - /** - * Visits a parenthesized expression that contains either a type assertion or an `as` - * expression. - * - * @param node The parenthesized expression node. - */ function visitParenthesizedExpression(node: ParenthesizedExpression): Expression { const innerExpression = skipOuterExpressions(node.expression, ~OuterExpressionKinds.Assertions); if (isAssertionExpression(innerExpression)) { @@ -2765,7 +2672,7 @@ namespace ts { const statements: Statement[] = []; startLexicalEnvironment(); const members = map(node.members, transformEnumMember); - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); addRange(statements, members); currentNamespaceContainerName = savedCurrentNamespaceLocalName; @@ -3086,7 +2993,7 @@ namespace ts { statementsLocation = moveRangePos(moduleBlock.statements, -1); } - addStatementsAfterPrologue(statements, endLexicalEnvironment()); + insertStatementsAfterStandardPrologue(statements, endLexicalEnvironment()); currentNamespaceContainerName = savedCurrentNamespaceContainerName; currentNamespace = savedCurrentNamespace; currentScopeFirstDeclarationsOfName = savedCurrentScopeFirstDeclarationsOfName; diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 0d24db8454e..c4ebb040b8d 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -5065,38 +5065,29 @@ namespace ts { // Facts // - Flags used to indicate that a node or subtree contains syntax that requires transformation. - TypeScript = 1 << 0, - ContainsTypeScript = 1 << 1, - ContainsJsx = 1 << 2, - ContainsESNext = 1 << 3, - ContainsES2017 = 1 << 4, - ContainsES2016 = 1 << 5, - ES2015 = 1 << 6, + ContainsTypeScript = 1 << 0, + ContainsJsx = 1 << 1, + ContainsESNext = 1 << 2, + ContainsES2019 = 1 << 3, + ContainsES2018 = 1 << 4, + ContainsES2017 = 1 << 5, + ContainsES2016 = 1 << 6, ContainsES2015 = 1 << 7, - Generator = 1 << 8, - ContainsGenerator = 1 << 9, - DestructuringAssignment = 1 << 10, - ContainsDestructuringAssignment = 1 << 11, + ContainsGenerator = 1 << 8, + ContainsDestructuringAssignment = 1 << 9, // Markers // - Flags used to indicate that a subtree contains a specific transformation. - ContainsTypeScriptClassSyntax = 1 << 12, // Decorators, Property Initializers, Parameter Property Initializers - ContainsLexicalThis = 1 << 13, - ContainsCapturedLexicalThis = 1 << 14, - ContainsLexicalThisInComputedPropertyName = 1 << 15, - ContainsDefaultValueAssignments = 1 << 16, - ContainsRestOrSpread = 1 << 17, - ContainsObjectRestOrSpread = 1 << 18, - ContainsComputedPropertyName = 1 << 19, - ContainsBlockScopedBinding = 1 << 20, - ContainsBindingPattern = 1 << 21, - ContainsYield = 1 << 22, - ContainsHoistedDeclarationOrCompletion = 1 << 23, - ContainsDynamicImport = 1 << 24, - Super = 1 << 25, - ContainsSuper = 1 << 26, - ContainsES2018 = 1 << 27, - ContainsES2019 = 1 << 28, + ContainsTypeScriptClassSyntax = 1 << 10, // Decorators, Property Initializers, Parameter Property Initializers + ContainsLexicalThis = 1 << 11, + ContainsRestOrSpread = 1 << 12, + ContainsObjectRestOrSpread = 1 << 13, + ContainsComputedPropertyName = 1 << 14, + ContainsBlockScopedBinding = 1 << 15, + ContainsBindingPattern = 1 << 16, + ContainsYield = 1 << 17, + ContainsHoistedDeclarationOrCompletion = 1 << 18, + ContainsDynamicImport = 1 << 19, // Please leave this as 1 << 29. // It is the maximum bit we can set before we outgrow the size of a v8 small integer (SMI) on an x86 system. @@ -5105,40 +5096,44 @@ namespace ts { // Assertions // - Bitmasks that are used to assert facts about the syntax of a node and its subtree. - AssertTypeScript = TypeScript | ContainsTypeScript, + AssertTypeScript = ContainsTypeScript, AssertJsx = ContainsJsx, AssertESNext = ContainsESNext, AssertES2019 = ContainsES2019, AssertES2018 = ContainsES2018, AssertES2017 = ContainsES2017, AssertES2016 = ContainsES2016, - AssertES2015 = ES2015 | ContainsES2015, - AssertGenerator = Generator | ContainsGenerator, - AssertDestructuringAssignment = DestructuringAssignment | ContainsDestructuringAssignment, + AssertES2015 = ContainsES2015, + AssertGenerator = ContainsGenerator, + AssertDestructuringAssignment = ContainsDestructuringAssignment, // Scope Exclusions // - Bitmasks that exclude flags from propagating out of a specific context // into the subtree flags of their container. - OuterExpressionExcludes = TypeScript | ES2015 | DestructuringAssignment | Generator | HasComputedFlags, - PropertyAccessExcludes = OuterExpressionExcludes | Super, - NodeExcludes = PropertyAccessExcludes | ContainsSuper, - ArrowFunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, - FunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsDefaultValueAssignments | ContainsCapturedLexicalThis | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, - ConstructorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, - MethodOrAccessorExcludes = NodeExcludes | ContainsDefaultValueAssignments | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, - ClassExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName, - ModuleExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsCapturedLexicalThis | ContainsBlockScopedBinding | ContainsHoistedDeclarationOrCompletion, + OuterExpressionExcludes = HasComputedFlags, + PropertyAccessExcludes = OuterExpressionExcludes, + NodeExcludes = PropertyAccessExcludes, + ArrowFunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, + FunctionExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, + ConstructorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, + MethodOrAccessorExcludes = NodeExcludes | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsYield | ContainsHoistedDeclarationOrCompletion | ContainsBindingPattern | ContainsObjectRestOrSpread, + PropertyExcludes = NodeExcludes | ContainsLexicalThis, + ClassExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsComputedPropertyName, + ModuleExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsLexicalThis | ContainsBlockScopedBinding | ContainsHoistedDeclarationOrCompletion, TypeExcludes = ~ContainsTypeScript, - ObjectLiteralExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsComputedPropertyName | ContainsLexicalThisInComputedPropertyName | ContainsObjectRestOrSpread, + ObjectLiteralExcludes = NodeExcludes | ContainsTypeScriptClassSyntax | ContainsComputedPropertyName | ContainsObjectRestOrSpread, ArrayLiteralOrCallOrNewExcludes = NodeExcludes | ContainsRestOrSpread, VariableDeclarationListExcludes = NodeExcludes | ContainsBindingPattern | ContainsObjectRestOrSpread, ParameterExcludes = NodeExcludes, CatchClauseExcludes = NodeExcludes | ContainsObjectRestOrSpread, BindingPatternExcludes = NodeExcludes | ContainsRestOrSpread, + // Propagating flags + // - Bitmasks for flags that should propagate from a child + PropertyNamePropagatingFlags = ContainsLexicalThis, + // Masks // - Additional bitmasks - ES2015FunctionSyntaxMask = ContainsCapturedLexicalThis | ContainsDefaultValueAssignments, } export interface SourceMapRange extends TextRange { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index aa2ebe3d60c..7cfacbb33eb 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -401,10 +401,7 @@ namespace ts { return !nodeIsMissing(node); } - /** - * Prepends statements to an array while taking care of prologue directives. - */ - export function addStatementsAfterPrologue(to: T[], from: ReadonlyArray | undefined): T[] { + function insertStatementsAfterPrologue(to: T[], from: ReadonlyArray | undefined, isPrologueDirective: (node: Node) => boolean): T[] { if (from === undefined || from.length === 0) return to; let statementIndex = 0; // skip all prologue directives to insert at the correct position @@ -417,6 +414,46 @@ namespace ts { return to; } + function insertStatementAfterPrologue(to: T[], statement: T | undefined, isPrologueDirective: (node: Node) => boolean): T[] { + if (statement === undefined) return to; + let statementIndex = 0; + // skip all prologue directives to insert at the correct position + for (; statementIndex < to.length; ++statementIndex) { + if (!isPrologueDirective(to[statementIndex])) { + break; + } + } + to.splice(statementIndex, 0, statement); + return to; + } + + + function isAnyPrologueDirective(node: Node) { + return isPrologueDirective(node) || !!(getEmitFlags(node) & EmitFlags.CustomPrologue); + } + + /** + * Prepends statements to an array while taking care of prologue directives. + */ + export function insertStatementsAfterStandardPrologue(to: T[], from: ReadonlyArray | undefined): T[] { + return insertStatementsAfterPrologue(to, from, isPrologueDirective); + } + + export function insertStatementsAfterCustomPrologue(to: T[], from: ReadonlyArray | undefined): T[] { + return insertStatementsAfterPrologue(to, from, isAnyPrologueDirective); + } + + /** + * Prepends statements to an array while taking care of prologue directives. + */ + export function insertStatementAfterStandardPrologue(to: T[], statement: T | undefined): T[] { + return insertStatementAfterPrologue(to, statement, isPrologueDirective); + } + + export function insertStatementAfterCustomPrologue(to: T[], statement: T | undefined): T[] { + return insertStatementAfterPrologue(to, statement, isAnyPrologueDirective); + } + /** * Determine if the given comment is a triple-slash * @@ -1436,6 +1473,11 @@ namespace ts { } } + export function isSuperOrSuperProperty(node: Node): node is SuperExpression | SuperProperty { + return node.kind === SyntaxKind.SuperKeyword + || isSuperProperty(node); + } + /** * Determines whether a node is a property or element access expression for `super`. */ @@ -3418,8 +3460,8 @@ namespace ts { return computeLineAndCharacterOfPosition(lineMap, pos).line; } - export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration | undefined { - return find(node.members, (member): member is ConstructorDeclaration => isConstructorDeclaration(member) && nodeIsPresent(member.body)); + export function getFirstConstructorWithBody(node: ClassLikeDeclaration): ConstructorDeclaration & { body: FunctionBody } | undefined { + return find(node.members, (member): member is ConstructorDeclaration & { body: FunctionBody } => isConstructorDeclaration(member) && nodeIsPresent(member.body)); } function getSetAccessorValueParameter(accessor: SetAccessorDeclaration): ParameterDeclaration | undefined { diff --git a/src/compiler/visitor.ts b/src/compiler/visitor.ts index 18b7c3f4a74..0d4d0d9482d 100644 --- a/src/compiler/visitor.ts +++ b/src/compiler/visitor.ts @@ -1478,8 +1478,8 @@ namespace ts { } return isNodeArray(statements) - ? setTextRange(createNodeArray(addStatementsAfterPrologue(statements.slice(), declarations)), statements) - : addStatementsAfterPrologue(statements, declarations); + ? setTextRange(createNodeArray(insertStatementsAfterStandardPrologue(statements.slice(), declarations)), statements) + : insertStatementsAfterStandardPrologue(statements, declarations); } /** diff --git a/tests/baselines/reference/arrowFunctionWithObjectLiteralBody5.js b/tests/baselines/reference/arrowFunctionWithObjectLiteralBody5.js index f9d924d6d9b..c097ccf0d50 100644 --- a/tests/baselines/reference/arrowFunctionWithObjectLiteralBody5.js +++ b/tests/baselines/reference/arrowFunctionWithObjectLiteralBody5.js @@ -11,4 +11,4 @@ var d = () => ((({ name: "foo", message: "bar" }))); var a = function () { return ({ name: "foo", message: "bar" }); }; var b = function () { return ({ name: "foo", message: "bar" }); }; var c = function () { return ({ name: "foo", message: "bar" }); }; -var d = function () { return (({ name: "foo", message: "bar" })); }; +var d = function () { return ({ name: "foo", message: "bar" }); }; diff --git a/tests/baselines/reference/arrowFunctionWithObjectLiteralBody6.js b/tests/baselines/reference/arrowFunctionWithObjectLiteralBody6.js index 6128a6bef86..270a9927835 100644 --- a/tests/baselines/reference/arrowFunctionWithObjectLiteralBody6.js +++ b/tests/baselines/reference/arrowFunctionWithObjectLiteralBody6.js @@ -11,4 +11,4 @@ var d = () => ((({ name: "foo", message: "bar" }))); var a = () => ({ name: "foo", message: "bar" }); var b = () => ({ name: "foo", message: "bar" }); var c = () => ({ name: "foo", message: "bar" }); -var d = () => (({ name: "foo", message: "bar" })); +var d = () => ({ name: "foo", message: "bar" }); diff --git a/tests/baselines/reference/decoratorOnClassMethod11.js b/tests/baselines/reference/decoratorOnClassMethod11.js index e29c4076c91..2600681c647 100644 --- a/tests/baselines/reference/decoratorOnClassMethod11.js +++ b/tests/baselines/reference/decoratorOnClassMethod11.js @@ -17,7 +17,6 @@ var __decorate = (this && this.__decorate) || function (decorators, target, key, }; var M; (function (M) { - var _this = this; var C = /** @class */ (function () { function C() { } diff --git a/tests/baselines/reference/dynamicImportWithNestedThis_es5.js b/tests/baselines/reference/dynamicImportWithNestedThis_es5.js index 39fa9fea4a7..81cbcc78e4e 100644 --- a/tests/baselines/reference/dynamicImportWithNestedThis_es5.js +++ b/tests/baselines/reference/dynamicImportWithNestedThis_es5.js @@ -30,7 +30,6 @@ c.dynamic(); } C.prototype.dynamic = function () { var _a; - var _this = this; return _a = this._path, __syncRequire ? Promise.resolve().then(function () { return require(_a); }) : new Promise(function (resolve_1, reject_1) { require([_a], resolve_1, reject_1); }); }; return C; diff --git a/tests/baselines/reference/invalidNewTarget.es5.js b/tests/baselines/reference/invalidNewTarget.es5.js index 0aaee863af1..56278dc56df 100644 --- a/tests/baselines/reference/invalidNewTarget.es5.js +++ b/tests/baselines/reference/invalidNewTarget.es5.js @@ -34,26 +34,44 @@ var C = /** @class */ (function () { this.f = function () { return _newTarget; }; } C.prototype[_newTarget] = function () { }; - C.prototype.c = function () { var _newTarget = void 0; return _newTarget; }; + C.prototype.c = function () { + var _newTarget = void 0; + return _newTarget; + }; Object.defineProperty(C.prototype, "d", { - get: function () { var _newTarget = void 0; return _newTarget; }, + get: function () { + var _newTarget = void 0; + return _newTarget; + }, enumerable: true, configurable: true }); Object.defineProperty(C.prototype, "e", { - set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + set: function (_) { + var _newTarget = void 0; + _ = _newTarget; + }, enumerable: true, configurable: true }); C[_newTarget] = function () { }; - C.g = function () { var _newTarget = void 0; return _newTarget; }; + C.g = function () { + var _newTarget = void 0; + return _newTarget; + }; Object.defineProperty(C, "h", { - get: function () { var _newTarget = void 0; return _newTarget; }, + get: function () { + var _newTarget = void 0; + return _newTarget; + }, enumerable: true, configurable: true }); Object.defineProperty(C, "i", { - set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + set: function (_) { + var _newTarget = void 0; + _ = _newTarget; + }, enumerable: true, configurable: true }); @@ -62,14 +80,23 @@ var C = /** @class */ (function () { }()); var O = (_a = {}, _a[_newTarget] = undefined, - _a.k = function () { var _newTarget = void 0; return _newTarget; }, + _a.k = function () { + var _newTarget = void 0; + return _newTarget; + }, Object.defineProperty(_a, "l", { - get: function () { var _newTarget = void 0; return _newTarget; }, + get: function () { + var _newTarget = void 0; + return _newTarget; + }, enumerable: true, configurable: true }), Object.defineProperty(_a, "m", { - set: function (_) { var _newTarget = void 0; _ = _newTarget; }, + set: function (_) { + var _newTarget = void 0; + _ = _newTarget; + }, enumerable: true, configurable: true }), diff --git a/tests/baselines/reference/newTarget.es5.js b/tests/baselines/reference/newTarget.es5.js index 5a27ed7efc7..7fa733880b4 100644 --- a/tests/baselines/reference/newTarget.es5.js +++ b/tests/baselines/reference/newTarget.es5.js @@ -49,11 +49,17 @@ var __extends = (this && this.__extends) || (function () { var A = /** @class */ (function () { function A() { var _newTarget = this.constructor; - this.d = function _a() { var _newTarget = this && this instanceof _a ? this.constructor : void 0; return _newTarget; }; + this.d = function _a() { + var _newTarget = this && this instanceof _a ? this.constructor : void 0; + return _newTarget; + }; var a = _newTarget; var b = function () { return _newTarget; }; } - A.c = function _a() { var _newTarget = this && this instanceof _a ? this.constructor : void 0; return _newTarget; }; + A.c = function _a() { + var _newTarget = this && this instanceof _a ? this.constructor : void 0; + return _newTarget; + }; return A; }()); var B = /** @class */ (function (_super) { @@ -78,5 +84,8 @@ var f2 = function _b() { var j = function () { return _newTarget; }; }; var O = { - k: function k() { var _newTarget = this && this instanceof k ? this.constructor : void 0; return _newTarget; } + k: function k() { + var _newTarget = this && this instanceof k ? this.constructor : void 0; + return _newTarget; + } }; diff --git a/tests/baselines/reference/noUnusedLocals_writeOnly.js b/tests/baselines/reference/noUnusedLocals_writeOnly.js index 55a927bfc2b..ec4f5a6f5ff 100644 --- a/tests/baselines/reference/noUnusedLocals_writeOnly.js +++ b/tests/baselines/reference/noUnusedLocals_writeOnly.js @@ -25,9 +25,9 @@ function f2(_: ReadonlyArray): void {} //// [noUnusedLocals_writeOnly.js] "use strict"; function f(x, b) { + var _a, _b; if (x === void 0) { x = 0; } if (b === void 0) { b = false; } - var _a, _b; // None of these statements read from 'x', so it will be marked unused. x = 1; x++; diff --git a/tests/baselines/reference/parseErrorIncorrectReturnToken.js b/tests/baselines/reference/parseErrorIncorrectReturnToken.js index 3d2a1d17a4a..52a6fcf25cc 100644 --- a/tests/baselines/reference/parseErrorIncorrectReturnToken.js +++ b/tests/baselines/reference/parseErrorIncorrectReturnToken.js @@ -17,9 +17,7 @@ let o = { string; // should be => not : // doesn't work in non-type contexts, where the return type is optional var f = function (n) { return function (string) { return n.toString(); }; }; -var o = { - m: function (n) { } -}; +var o = {}; string; { return n.toString(); diff --git a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.js b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.js index 83586363466..5b4b71cab1f 100644 --- a/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.js +++ b/tests/baselines/reference/parserErrantEqualsGreaterThanAfterFunction2.js @@ -2,5 +2,4 @@ function f(p: A) => p; //// [parserErrantEqualsGreaterThanAfterFunction2.js] -function f(p) { } p; diff --git a/tests/baselines/reference/strictModeInConstructor.js b/tests/baselines/reference/strictModeInConstructor.js index 0e25c4496fb..4356793d3d2 100644 --- a/tests/baselines/reference/strictModeInConstructor.js +++ b/tests/baselines/reference/strictModeInConstructor.js @@ -123,8 +123,8 @@ var Bs = /** @class */ (function (_super) { var Cs = /** @class */ (function (_super) { __extends(Cs, _super); function Cs() { - var _this = _super.call(this) || this; "use strict"; + var _this = _super.call(this) || this; return _this; } Cs.s = 9; diff --git a/tests/baselines/reference/thisInConstructorParameter2.js b/tests/baselines/reference/thisInConstructorParameter2.js index fc1525f03cf..df2f22b8ae7 100644 --- a/tests/baselines/reference/thisInConstructorParameter2.js +++ b/tests/baselines/reference/thisInConstructorParameter2.js @@ -12,16 +12,15 @@ class P { } //// [thisInConstructorParameter2.js] -var _this = this; var P = /** @class */ (function () { function P(z, zz, zzz) { + var _this = this; if (z === void 0) { z = this; } if (zz === void 0) { zz = this; } if (zzz === void 0) { zzz = function (p) { if (p === void 0) { p = _this; } return _this; }; } - var _this = this; this.z = z; this.x = this; zzz = function (p) { diff --git a/tests/baselines/reference/thisInInvalidContexts.js b/tests/baselines/reference/thisInInvalidContexts.js index 9ce5071c903..71c0a408c45 100644 --- a/tests/baselines/reference/thisInInvalidContexts.js +++ b/tests/baselines/reference/thisInInvalidContexts.js @@ -62,7 +62,6 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -var _this = this; //'this' in static member initializer var ErrClass1 = /** @class */ (function () { function ErrClass1() { diff --git a/tests/baselines/reference/thisInInvalidContextsExternalModule.js b/tests/baselines/reference/thisInInvalidContextsExternalModule.js index 92f6afc8c29..e295c49074b 100644 --- a/tests/baselines/reference/thisInInvalidContextsExternalModule.js +++ b/tests/baselines/reference/thisInInvalidContextsExternalModule.js @@ -63,7 +63,6 @@ var __extends = (this && this.__extends) || (function () { d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __()); }; })(); -var _this = this; //'this' in static member initializer var ErrClass1 = /** @class */ (function () { function ErrClass1() { diff --git a/tests/baselines/reference/thisInOuterClassBody.js b/tests/baselines/reference/thisInOuterClassBody.js index d5bf689b49a..2b4e3a1aabd 100644 --- a/tests/baselines/reference/thisInOuterClassBody.js +++ b/tests/baselines/reference/thisInOuterClassBody.js @@ -21,7 +21,6 @@ class Foo { } //// [thisInOuterClassBody.js] -var _this = this; var Foo = /** @class */ (function () { function Foo() { this.x = this; diff --git a/tests/baselines/reference/thisTypeInFunctionsNegative.js b/tests/baselines/reference/thisTypeInFunctionsNegative.js index 9150536f4e7..ed454ee8b70 100644 --- a/tests/baselines/reference/thisTypeInFunctionsNegative.js +++ b/tests/baselines/reference/thisTypeInFunctionsNegative.js @@ -321,7 +321,6 @@ function modifiers() { return this.n; } function restParam(...) { return this.n; } function optional() { return this.n; } function decorated() { return this.n; } -function initializer(, C) { } (); number; { diff --git a/tests/baselines/reference/typeOfThisInStaticMembers2.js b/tests/baselines/reference/typeOfThisInStaticMembers2.js index 1cfec3a3fa1..72243cdd3e6 100644 --- a/tests/baselines/reference/typeOfThisInStaticMembers2.js +++ b/tests/baselines/reference/typeOfThisInStaticMembers2.js @@ -8,7 +8,6 @@ class C2 { } //// [typeOfThisInStaticMembers2.js] -var _this = this; var C = /** @class */ (function () { function C() { }