diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 1baf1847f04..3e02558117b 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -13,7 +13,10 @@ namespace ts { getTrailingCommentsOfPosition(pos: number): CommentRange[]; emitLeadingComments(range: TextRange, comments: CommentRange[]): void; emitTrailingComments(range: TextRange, comments: CommentRange[]): void; - emitDetachedComments(range: TextRange): void; + emitLeadingDetachedComments(range: TextRange): void; + emitLeadingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void; + emitTrailingDetachedComments(range: TextRange): void; + emitTrailingDetachedComments(range: TextRange, contextNode: Node, shouldSkipCommentsForNodeCallback: (node: Node) => boolean): void; } export function createCommentWriter(host: EmitHost, writer: EmitTextWriter, sourceMap: SourceMapWriter): CommentWriter { @@ -46,10 +49,15 @@ namespace ts { getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, emitLeadingComments(range: TextRange, comments: CommentRange[]): void { }, emitTrailingComments(range: TextRange, comments: CommentRange[]): void { }, - emitDetachedComments, + emitLeadingDetachedComments, + emitTrailingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void {} }; - function emitDetachedComments(node: TextRange): void { + function emitLeadingDetachedComments(node: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + emitDetachedCommentsAndUpdateCommentsInfo(node, /*removeComments*/ true); } } @@ -65,7 +73,8 @@ namespace ts { getTrailingCommentsOfPosition, emitLeadingComments, emitTrailingComments, - emitDetachedComments, + emitLeadingDetachedComments, + emitTrailingDetachedComments }; function getLeadingComments(range: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): CommentRange[]; @@ -154,10 +163,23 @@ namespace ts { emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); } - function emitDetachedComments(range: TextRange) { + function emitLeadingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); } + function emitTrailingDetachedComments(range: TextRange, contextNode?: Node, shouldSkipCommentsForNodeCallback?: (node: Node) => boolean): void { + if (shouldSkipCommentsForNodeCallback && shouldSkipCommentsForNodeCallback(contextNode)) { + return; + } + + range = collapseRangeToEnd(range); + emitLeadingComments(range, getLeadingComments(range)); + } + function hasConsumedCommentRange(comment: CommentRange) { return comment.end === consumedCommentRanges[comment.pos]; } diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 928ba76699e..8721a03ad71 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -651,13 +651,14 @@ namespace ts { /** * Creates a synthetic expression to act as a placeholder for a not-emitted expression in - * order to preserve comments. + * order to preserve comments or sourcemap positions. * * @param expression The inner expression to emit. * @param original The original outer expression. + * @param location The location for the expression. Defaults to the positions from "original" if provided. */ - export function createPartiallyEmittedExpression(expression: Expression, original: Node) { - const node = createNode(SyntaxKind.PartiallyEmittedExpression, /*location*/ original); + export function createPartiallyEmittedExpression(expression: Expression, original?: Node, location?: TextRange) { + const node = createNode(SyntaxKind.PartiallyEmittedExpression, /*location*/ location || original); node.expression = expression; node.original = original; return node; @@ -1257,7 +1258,7 @@ namespace ts { * The function needs to be called during each transformation step. * This function needs to be called whenever we transform the statement * list of a source file, namespace, or function-like body. - * + * * @param target: result statements array * @param source: origin statements array * @param ensureUseStrict: boolean determining whether the function need to add prologue-directives diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 619b3745e70..b5cc8206688 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -140,7 +140,8 @@ const _super = (function (geti, seti) { getTrailingCommentsOfPosition, emitLeadingComments, emitTrailingComments, - emitDetachedComments + emitLeadingDetachedComments, + emitTrailingDetachedComments } = comments; let context: TransformationContext; @@ -298,24 +299,45 @@ const _super = (function (geti, seti) { const leadingComments = getLeadingComments(node, shouldSkipCommentsForNode); const trailingComments = getTrailingComments(node, shouldSkipCommentsForNode); emitLeadingComments(node, leadingComments); - emitStart(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren); + emitStart(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren); emitWorker(node); - emitEnd(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren); + emitEnd(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren); emitTrailingComments(node, trailingComments); } } + /** + * Determines whether to skip comment emit for a node. + * + * We do not emit comments for NotEmittedStatement nodes or any node that has + * NodeEmitFlags.NoComments. + * + * @param node A Node. + */ function shouldSkipCommentsForNode(node: Node) { return isNotEmittedStatement(node) || (getNodeEmitFlags(node) & NodeEmitFlags.NoComments) !== 0; } - function shouldIgnoreSourceMapForNode(node: Node) { - return isNotEmittedOrPartiallyEmittedNode(node) + /** + * Determines whether to skip source map emit for a node. + * + * We do not emit source maps for NotEmittedStatement nodes or any node that + * has NodeEmitFlags.NoSourceMap. + * + * @param node A Node. + */ + function shouldSkipSourceMapForNode(node: Node) { + return isNotEmittedStatement(node) || (getNodeEmitFlags(node) & NodeEmitFlags.NoSourceMap) !== 0; } - function shouldIgnoreSourceMapForChildren(node: Node) { + /** + * Determines whether to skip source map emit for a node and its children. + * + * We do not emit source maps for a node that has NodeEmitFlags.NoNestedSourceMaps. + */ + function shouldSkipSourceMapForChildren(node: Node) { return (getNodeEmitFlags(node) & NodeEmitFlags.NoNestedSourceMaps) !== 0; } @@ -1064,7 +1086,6 @@ const _super = (function (geti, seti) { emitDecorators(node, node.decorators); emitModifiers(node, node.modifiers); emitSignatureAndBody(node, emitArrowFunctionHead); - } function emitArrowFunctionHead(node: ArrowFunction) { @@ -1375,7 +1396,8 @@ const _super = (function (geti, seti) { } function emitDebuggerStatement(node: DebuggerStatement) { - write("debugger;"); + writeToken(SyntaxKind.DebuggerKeyword, node.pos); + write(";"); } // @@ -1417,9 +1439,7 @@ const _super = (function (geti, seti) { tempFlags = 0; startLexicalEnvironment(); emitSignatureHead(node); - write(" {"); - emitBlockFunctionBody(node, body); - writeToken(SyntaxKind.CloseBraceToken, node.end) + emitBlockFunctionBodyAndEndLexicalEnvironment(node, body); if (indentedFlag) { decreaseIndent(); } @@ -1483,10 +1503,12 @@ const _super = (function (geti, seti) { return true; } - function emitBlockFunctionBody(parentNode: Node, body: Block) { + function emitBlockFunctionBodyAndEndLexicalEnvironment(parentNode: Node, body: Block) { + write(" {"); + const startingLine = writer.getLine(); increaseIndent(); - emitDetachedComments(body.statements); + emitLeadingDetachedComments(body.statements, body, shouldSkipCommentsForNode); // Emit all the prologue directives (like "use strict"). const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); @@ -1503,10 +1525,9 @@ const _super = (function (geti, seti) { const endingLine = writer.getLine(); emitLexicalEnvironment(endLexicalEnvironment(), /*newLine*/ startingLine !== endingLine); - - const range = collapseRangeToEnd(body.statements); - emitLeadingComments(range, getLeadingComments(range)); + emitTrailingDetachedComments(body.statements, body, shouldSkipCommentsForNode); decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end); } function emitClassDeclaration(node: ClassDeclaration) { @@ -1882,7 +1903,7 @@ const _super = (function (geti, seti) { function emitSourceFile(node: SourceFile) { writeLine(); emitShebang(); - emitDetachedComments(node); + emitLeadingDetachedComments(node); const statements = node.statements; const statementOffset = emitPrologueDirectives(statements); @@ -1900,7 +1921,7 @@ const _super = (function (geti, seti) { tempFlags = savedTempFlags; } - emitLeadingComments(node.endOfFileToken, getLeadingComments(node.endOfFileToken)); + emitTrailingDetachedComments(node.statements); } // Transformation nodes @@ -2281,6 +2302,7 @@ const _super = (function (geti, seti) { } function writeToken(token: SyntaxKind, tokenStartPos: number) { + tokenStartPos = skipTrivia(currentText, tokenStartPos); emitPos(tokenStartPos); const tokenEndPos = writeTokenText(token, tokenStartPos); emitPos(tokenEndPos); @@ -2295,9 +2317,9 @@ const _super = (function (geti, seti) { function writeTokenNode(node: Node) { if (node) { - emitStart(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren); + emitStart(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren); writeTokenText(node.kind); - emitEnd(node, shouldIgnoreSourceMapForNode, shouldIgnoreSourceMapForChildren); + emitEnd(node, shouldSkipSourceMapForNode, shouldSkipSourceMapForChildren); } } diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 6aa373a4ef4..b66e7cc0b5a 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -156,6 +156,7 @@ namespace ts { context.expressionSubstitution = substituteExpression; let currentSourceFile: SourceFile; + let currentText: string; let currentParent: Node; let currentNode: Node; let enclosingBlockScopeContainer: Node; @@ -183,6 +184,7 @@ namespace ts { function transformSourceFile(node: SourceFile) { currentSourceFile = node; + currentText = node.text; return visitNode(node, visitor, isSourceFile); } @@ -609,14 +611,14 @@ namespace ts { if (node.name) { enableSubstitutionsForBlockScopedBindings(); } + const closingBraceLocation = { pos: node.end - 1, end: node.end }; const baseTypeNode = getClassExtendsHeritageClauseElement(node); const classFunction = createFunctionExpression( /*asteriskToken*/ undefined, /*name*/ undefined, baseTypeNode ? [createParameter("_super")] : [], - transformClassBody(node, baseTypeNode !== undefined, closingBraceLocation), - closingBraceLocation + transformClassBody(node, baseTypeNode !== undefined) ); // To preserve the behavior of the old emitter, we explicitly indent @@ -626,15 +628,23 @@ namespace ts { setNodeEmitFlags(classFunction, NodeEmitFlags.Indented); } + // "inner" and "outer" below are added purely to preserve source map locations from + // the old emitter + const inner = createPartiallyEmittedExpression(classFunction); + inner.end = node.end; + setNodeEmitFlags(inner, NodeEmitFlags.NoComments); + + const outer = createPartiallyEmittedExpression(inner); + outer.end = node.pos; + setNodeEmitFlags(outer, NodeEmitFlags.NoComments); + return createParen( createCall( - classFunction, + outer, baseTypeNode ? [visitNode(baseTypeNode.expression, visitor, isExpression)] - : [], - closingBraceLocation - ), - closingBraceLocation + : [] + ) ); } @@ -644,15 +654,33 @@ namespace ts { * @param node A ClassExpression or ClassDeclaration node. * @param hasExtendsClause A value indicating whether the class has an `extends` clause. */ - function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean, closingBraceLocation: TextRange): Block { + function transformClassBody(node: ClassExpression | ClassDeclaration, hasExtendsClause: boolean): Block { const statements: Statement[] = []; startLexicalEnvironment(); addExtendsHelperIfNeeded(statements, node, hasExtendsClause); addConstructor(statements, node, hasExtendsClause); addClassMembers(statements, node); - statements.push(createReturn(getDeclarationName(node), /*location*/ closingBraceLocation)); + + // Create a synthetic text range for the return statement. + const closingBraceLocation = createTokenRange(skipTrivia(currentText, node.members.end), SyntaxKind.CloseBraceToken); + const name = getDeclarationName(node); + + // The following partially-emitted expression exists purely to align our sourcemap + // emit with the original emitter. + const outer = createPartiallyEmittedExpression(name); + outer.end = closingBraceLocation.end; + setNodeEmitFlags(outer, NodeEmitFlags.NoComments); + + const statement = createReturn(outer); + statement.pos = closingBraceLocation.pos; + statements.push(statement); + setNodeEmitFlags(statement, NodeEmitFlags.NoComments); + addRange(statements, endLexicalEnvironment()); - return createBlock(statements, /*location*/ undefined, /*multiLine*/ true); + const block = createBlock(createNodeArray(statements, /*location*/ node.members), /*location*/ undefined, /*multiLine*/ true); + setNodeEmitFlags(block, NodeEmitFlags.NoComments); + + return block; } /** @@ -688,7 +716,7 @@ namespace ts { /*asteriskToken*/ undefined, getDeclarationName(node), transformConstructorParameters(constructor, hasSynthesizedSuper), - transformConstructorBody(constructor, hasExtendsClause, hasSynthesizedSuper), + transformConstructorBody(constructor, node, hasExtendsClause, hasSynthesizedSuper), /*location*/ constructor || node ) ); @@ -718,11 +746,12 @@ namespace ts { * Transforms the body of a constructor declaration of a class. * * @param constructor The constructor for the class. + * @param node The node which contains the constructor. * @param hasExtendsClause A value indicating whether the class has an `extends` clause. * @param hasSynthesizedSuper A value indicating whether the constructor starts with a * synthesized `super` call. */ - function transformConstructorBody(constructor: ConstructorDeclaration, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { + function transformConstructorBody(constructor: ConstructorDeclaration, node: ClassDeclaration | ClassExpression, hasExtendsClause: boolean, hasSynthesizedSuper: boolean) { const statements: Statement[] = []; startLexicalEnvironment(); if (constructor) { @@ -739,14 +768,20 @@ namespace ts { } addRange(statements, endLexicalEnvironment()); - return createBlock( + const block = createBlock( createNodeArray( statements, - /*location*/ constructor ? constructor.body.statements : undefined + /*location*/ constructor ? constructor.body.statements : node.members ), - /*location*/ constructor ? constructor.body : undefined, + /*location*/ constructor ? constructor.body : node, /*multiLine*/ true ); + + if (!constructor) { + setNodeEmitFlags(block, NodeEmitFlags.NoComments); + } + + return block; } function transformConstructorBodyWithSynthesizedSuper(node: ConstructorDeclaration) { @@ -1077,16 +1112,24 @@ namespace ts { * @param member The MethodDeclaration node. */ function transformClassMethodDeclarationToStatement(receiver: LeftHandSideExpression, member: MethodDeclaration) { - return createStatement( + const statement = createStatement( createAssignment( createMemberAccessForPropertyName( receiver, - visitNode(member.name, visitor, isPropertyName) + visitNode(member.name, visitor, isPropertyName), + /*location*/ member.name ), - transformFunctionLikeToExpression(member, /*location*/ undefined, /*name*/ undefined) + transformFunctionLikeToExpression(member, /*location*/ member, /*name*/ undefined), + /*location*/ moveRangeEnd(member, -1) ), /*location*/ member ); + + // The location for the statement is used to emit comments only. + // No source map should be emitted for this statement to align with the + // old emitter. + setNodeEmitFlags(statement, NodeEmitFlags.NoSourceMap); + return statement; } /** @@ -1096,9 +1139,16 @@ namespace ts { * @param accessors The set of related get/set accessors. */ function transformAccessorsToStatement(receiver: LeftHandSideExpression, accessors: AllAccessorDeclarations): Statement { - return createStatement( - transformAccessorsToExpression(receiver, accessors) + const statement = createStatement( + transformAccessorsToExpression(receiver, accessors), + /*location*/ accessors.firstAccessor ); + + // The location for the statement is used to emit source maps only. + // No comments should be emitted for this statement to align with the + // old emitter. + setNodeEmitFlags(statement, NodeEmitFlags.NoComments); + return statement; } /** @@ -1122,7 +1172,7 @@ namespace ts { configurable: true }, /*preferNewLine*/ true, - /*location*/ firstAccessor, + /*location*/ undefined, /*descriptorLocations*/ { get: getAccessor, set: setAccessor @@ -1224,6 +1274,7 @@ namespace ts { // addPrologueDirectives will simply put already-existing directives at the beginning of the target statement-array statementOffset = addPrologueDirectives(statements, body.statements, /*ensureUseStrict*/ false); } + addCaptureThisForNodeIfNeeded(statements, node); addDefaultValueAssignmentsIfNeeded(statements, node); addRestParameterIfNeeded(statements, node, /*inConstructorWithSynthesizedSuper*/ false); @@ -1244,7 +1295,12 @@ namespace ts { } else { Debug.assert(node.kind === SyntaxKind.ArrowFunction); - statementsLocation = body; + + // To align with the old emitter, we use a synthetic end position on the location + // for the statement list we synthesize when we down-level an arrow function with + // an expression function body. This prevents both comments and source maps from + // being emitted for the end position only. + statementsLocation = moveRangeEnd(body, -1); const equalsGreaterThanToken = (node).equalsGreaterThanToken; if (!nodeIsSynthesized(equalsGreaterThanToken) && !nodeIsSynthesized(body)) { @@ -1258,7 +1314,7 @@ namespace ts { const expression = visitNode(body, visitor, isExpression); if (expression) { - statements.push(createReturn(expression)); + statements.push(createReturn(expression, /*location*/ statementsLocation)); } } diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index bf6881b9810..8b09e521d51 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -2943,12 +2943,63 @@ namespace ts { } } - export function collapseRangeToStart(range: TextRange) { - return range.pos === range.end ? range : { pos: range.pos, end: range.pos }; + /** + * Creates a new TextRange from a provided range with a new end position. + * + * @param range A TextRange. + * @param end The new end position. + */ + export function moveRangeEnd(range: TextRange, end: number): TextRange { + return { pos: range.pos, end }; } - export function collapseRangeToEnd(range: TextRange) { - return range.pos === range.end ? range : { pos: range.end, end: range.end }; + /** + * Creates a new TextRange from a provided range with a new start position. + * + * @param range A TextRange. + * @param pos The new Start position. + */ + export function moveRangePos(range: TextRange, pos: number): TextRange { + return { pos, end: range.end }; + } + + /** + * Determines whether a TextRange has the same start and end positions. + * + * @param range A TextRange. + */ + export function isCollapsedRange(range: TextRange) { + return range.pos === range.end; + } + + /** + * Creates a new TextRange from a provided range with its end position collapsed to its + * start position. + * + * @param range A TextRange. + */ + export function collapseRangeToStart(range: TextRange): TextRange { + return isCollapsedRange(range) ? range : moveRangeEnd(range, range.pos); + } + + /** + * Creates a new TextRange from a provided range with its start position collapsed to its + * end position. + * + * @param range A TextRange. + */ + export function collapseRangeToEnd(range: TextRange): TextRange { + return isCollapsedRange(range) ? range : moveRangePos(range, range.end); + } + + /** + * Creates a new TextRange for a token at the provides start position. + * + * @param pos The start position. + * @param token The token. + */ + export function createTokenRange(pos: number, token: SyntaxKind): TextRange { + return { pos, end: pos + tokenToString(token).length }; } export function rangeIsOnSingleLine(range: TextRange, sourceFile: SourceFile) { diff --git a/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js index e29d4720cbc..bbe190f65b9 100644 --- a/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js +++ b/tests/baselines/reference/contextualTypeWithUnionTypeCallSignatures.js @@ -45,6 +45,6 @@ var x = function (a) { return a.toString(); }; var x2 = function (a) { return a.toString(); }; // Like iWithCallSignatures var x2 = function (a) { return a; }; // Like iWithCallSignatures2 // With call signatures of mismatching parameter type -var x3 = function (a) { return a.toString(); }; +var x3 = function (a) { /*here a should be any*/ return a.toString(); }; // With call signature count mismatch -var x4 = function (a) { return a.toString(); }; +var x4 = function (a) { /*here a should be any*/ return a.toString(); }; diff --git a/tests/baselines/reference/disallowLineTerminatorBeforeArrow.js b/tests/baselines/reference/disallowLineTerminatorBeforeArrow.js index 55fba8115e0..1044146ccd0 100644 --- a/tests/baselines/reference/disallowLineTerminatorBeforeArrow.js +++ b/tests/baselines/reference/disallowLineTerminatorBeforeArrow.js @@ -110,9 +110,7 @@ var f8 = function (x, y, z) { var f9 = function (a) { return a; }; var f10 = function (a) { return a; }; var f11 = function (a) { return a; }; -var f12 = function (a) { - return a; -}; +var f12 = function (a) { return a; }; // Should be valid. var f11 = function (a) { return a; }; // Should be valid.