diff --git a/src/compiler/binder.ts b/src/compiler/binder.ts index 9986a2b5a05..51918f70cc6 100644 --- a/src/compiler/binder.ts +++ b/src/compiler/binder.ts @@ -3839,6 +3839,7 @@ namespace ts { case SyntaxKind.ThisKeyword: // Mark this node and its ancestors as containing a lexical `this` keyword. + transformFlags |= TransformFlags.AssertES2015; transformFlags |= TransformFlags.ContainsLexicalThis; break; diff --git a/src/compiler/transformers/es2015.ts b/src/compiler/transformers/es2015.ts index 0808a68c047..3e05ce8ec98 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 @@ -262,7 +238,6 @@ namespace ts { SubtreeFactsMask = ~AncestorFactsMask, - PropagateNewTargetMask = NewTarget, ArrowFunctionSubtreeExcludes = None, FunctionSubtreeExcludes = NewTarget | LexicalThis | CapturedLexicalThis, } @@ -528,22 +503,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); + insertCaptureThisForNodeIfNeeded(prologue, node); + 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()); + insertStatementsAfterStandardPrologue(prologue, endLexicalEnvironment()); exitSubtree(ancestorFacts, HierarchyFacts.None, HierarchyFacts.None); return updateSourceFileNode( node, - setTextRange(createNodeArray(statements), node.statements) + setTextRange(createNodeArray(concatenate(prologue, statements)), node.statements) ); } @@ -597,6 +573,9 @@ namespace ts { function visitThisKeyword(node: Node): Node { hierarchyFacts |= HierarchyFacts.LexicalThis; + 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. @@ -848,7 +827,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); @@ -926,6 +905,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. * @@ -935,82 +936,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 && !(hierarchyFacts & HierarchyFacts.LexicalThis)) { + // 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; } @@ -1044,104 +1116,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); } @@ -1232,11 +1206,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 { + function addDefaultValueAssignmentsIfNeeded(statements: Statement[], node: FunctionLikeDeclaration): boolean { if (!some(node.parameters, hasDefaultValueOrBindingPattern)) { - return; + return false; } + let added = false; for (const parameter of node.parameters) { const { name, initializer, dotDotDotToken } = parameter; @@ -1247,12 +1222,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; } /** @@ -1263,14 +1240,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, @@ -1280,27 +1256,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; } /** @@ -1311,7 +1291,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"), @@ -1340,7 +1320,7 @@ namespace ts { startOnNewLine(statement); setTextRange(statement, parameter); setEmitFlags(statement, EmitFlags.NoTokenSourceMaps | EmitFlags.NoTrailingSourceMap | EmitFlags.CustomPrologue | EmitFlags.NoComments); - statements.push(statement); + insertStatementAfterCustomPrologue(statements, statement); } /** @@ -1364,10 +1344,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. @@ -1380,7 +1361,7 @@ namespace ts { const temp = createLoopVariable(); // var param = []; - statements.push( + prologueStatements.push( setEmitFlags( setTextRange( createVariableStatement( @@ -1439,11 +1420,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( @@ -1458,6 +1439,9 @@ namespace ts { ) ); } + + insertStatementsAfterCustomPrologue(statements, prologueStatements); + return true; } /** @@ -1466,13 +1450,15 @@ namespace ts { * @param statements The statements for the new function body. * @param node A node. */ - function addCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): void { + function insertCaptureThisForNodeIfNeeded(statements: Statement[], node: Node): boolean { if (node.transformFlags & TransformFlags.ContainsCapturedLexicalThis && node.kind !== SyntaxKind.ArrowFunction) { - captureThisForNode(statements, node, createThis()); + 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, @@ -1486,10 +1472,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) { @@ -1549,11 +1535,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; @@ -1716,9 +1704,6 @@ namespace ts { * @param node An ArrowFunction node. */ function visitArrowFunction(node: ArrowFunction) { - if (node.transformFlags & TransformFlags.ContainsLexicalThis) { - enableSubstitutionsForCapturedThis(); - } const savedConvertedLoopState = convertedLoopState; convertedLoopState = undefined; const ancestorFacts = enterSubtree(HierarchyFacts.ArrowFunctionExcludes, HierarchyFacts.ArrowFunctionIncludes); @@ -1734,7 +1719,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; } @@ -1862,7 +1854,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; @@ -1871,16 +1863,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)); @@ -1921,16 +1912,16 @@ 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); + const block = createBlock(setTextRange(createNodeArray(concatenate(prologue, statements)), statementsLocation), multiLine); setTextRange(block, node.body); if (!multiLine && singleLine) { setEmitFlags(block, EmitFlags.SingleLine); @@ -1950,7 +1941,7 @@ namespace ts { updated, setTextRange( createNodeArray( - prependCaptureNewTargetIfNeeded(updated.statements as MutableNodeArray, node, /*copyOnWrite*/ true) + insertCaptureNewTargetIfNeeded(updated.statements as MutableNodeArray, node, /*copyOnWrite*/ true) ), /*location*/ updated.statements ) @@ -3107,7 +3098,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); @@ -3775,7 +3766,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) ); } @@ -3791,19 +3782,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) 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..91d9349d52a 100644 --- a/src/compiler/transformers/generators.ts +++ b/src/compiler/transformers/generators.ts @@ -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..5c68da23821 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) { diff --git a/src/compiler/transformers/module/system.ts b/src/compiler/transformers/module/system.ts index 82815528c21..a25e8c31c4a 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([ diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index 702deeb9d81..5774ff8d0fc 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -675,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); @@ -2672,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; @@ -2993,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/utilities.ts b/src/compiler/utilities.ts index 4f653aedd18..ef8a1dd9653 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 * @@ -3418,8 +3455,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/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/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/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..584418f419d 100644 --- a/tests/baselines/reference/thisInConstructorParameter2.js +++ b/tests/baselines/reference/thisInConstructorParameter2.js @@ -15,13 +15,13 @@ class P { 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) {