diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts index 9ed15335541..e8db28d90db 100644 --- a/src/compiler/comments.ts +++ b/src/compiler/comments.ts @@ -5,18 +5,9 @@ namespace ts { export interface CommentWriter { reset(): void; setSourceFile(sourceFile: SourceFile): void; - getLeadingComments(range: TextRange): CommentRange[]; - getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; - getTrailingComments(range: TextRange): CommentRange[]; - getTrailingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; - getTrailingCommentsOfPosition(pos: number): CommentRange[]; - emitLeadingComments(range: TextRange, comments: CommentRange[]): void; - emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode: Node, getTextRangeCallback: (contextNode: Node) => TextRange): void; - emitTrailingComments(range: TextRange, comments: CommentRange[]): void; - emitLeadingDetachedComments(range: TextRange): void; - emitLeadingDetachedComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean): void; - emitTrailingDetachedComments(range: TextRange): void; - emitTrailingDetachedComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean): void; + emitNodeWithComments(node: Node, emitCallback: (node: Node) => void): void; + emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void; + emitTrailingCommentsOfPosition(pos: number): void; } export function createCommentWriter(host: EmitHost, writer: EmitTextWriter, sourceMap: SourceMapWriter): CommentWriter { @@ -24,250 +15,177 @@ namespace ts { const newLine = host.getNewLine(); const { emitPos } = sourceMap; + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; let currentSourceFile: SourceFile; let currentText: string; let currentLineMap: number[]; let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; - // This maps start->end for a comment range. See `hasConsumedCommentRange` and - // `consumeCommentRange` for usage. + // Tracks comment ranges that have already been consumed. let consumedCommentRanges: Map; - let leadingCommentRangePositions: Map; - let trailingCommentRangePositions: Map; - const commentWriter = compilerOptions.removeComments - ? createCommentRemovingWriter() - : createCommentPreservingWriter(); + return { + reset, + setSourceFile, + emitNodeWithComments, + emitBodyWithDetachedComments, + emitTrailingCommentsOfPosition, + }; - return compilerOptions.extendedDiagnostics - ? createCommentWriterWithExtendedDiagnostics(commentWriter) - : commentWriter; - - function createCommentRemovingWriter(): CommentWriter { - return { - reset, - setSourceFile, - getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { return undefined; }, - getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { return undefined; }, - getTrailingCommentsOfPosition(pos: number): CommentRange[] { return undefined; }, - emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { }, - emitTrailingComments(range: TextRange, comments: CommentRange[]): void { }, - emitLeadingDetachedComments, - emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void {} - }; - - function emitLeadingDetachedComments(node: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { - if (ignoreNodeCallback && ignoreNodeCallback(contextNode)) { - return; - } - - emitDetachedCommentsAndUpdateCommentsInfo(node, /*removeComments*/ true); + function emitNodeWithComments(node: Node, emitCallback: (node: Node) => void) { + if (compilerOptions.removeComments) { + emitCallback(node); + return; } - } - function createCommentPreservingWriter(): CommentWriter { - const noComments: CommentRange[] = []; - return { - reset, - setSourceFile, - getLeadingComments, - getTrailingComments, - getTrailingCommentsOfPosition, - emitLeadingComments, - emitTrailingComments, - emitLeadingDetachedComments, - emitTrailingDetachedComments - }; + if (node) { + const { pos, end } = node.commentRange || node; + if ((pos < 0 && end < 0) || (pos === end)) { + // Both pos and end are synthesized, so just emit the node without comments. + emitCallback(node); + } + else { + const emitFlags = node.emitFlags; + const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement; + const skipLeadingComments = pos < 0 || (emitFlags & NodeEmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = end < 0 || (emitFlags & NodeEmitFlags.NoTrailingComments) !== 0; - function getLeadingComments(range: TextRange): CommentRange[]; - function getLeadingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; - function getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { - let comments: CommentRange[] = []; - let ignored = false; - if (contextNode) { - range = getTextRangeCallback(contextNode) || range; - if (ignoreNodeCallback(contextNode)) { - ignored = true; - // If the node will not be emitted in JS, remove all the comments (normal, - // pinned and `///`) associated with the node, unless it is a triple slash - // comment at the top of the file. - // - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // - // The first `///` will NOT be removed while the second one will be removed - // even though both nodes will not be emitted. - if (range.pos === 0) { - comments = filter(getLeadingCommentsOfPosition(0), isTripleSlashComment); + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. + if (!skipLeadingComments) { + emitLeadingComments(pos, isEmittedNode); + } + + // Save current container state on the stack. + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + + if (!skipLeadingComments) { + containerPos = pos; + } + + if (!skipTrailingComments) { + containerEnd = end; + + // To avoid invalid comment emit in a down-level binding pattern, we + // keep track of the last declaration list container's end + if (node.kind === SyntaxKind.VariableDeclarationList) { + declarationListContainerEnd = end; } } - } - if (!ignored) { - comments = getLeadingCommentsOfPosition(range.pos); - } + emitCallback(node); - return comments; - } + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; - /** - * Determine if the given comment is a triple-slash - **/ - function isTripleSlashComment(comment: CommentRange) { - // Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text - // so that we don't end up computing comment string and doing match for all // comments - if (currentText.charCodeAt(comment.pos + 1) === CharacterCodes.slash && - comment.pos + 2 < comment.end && - currentText.charCodeAt(comment.pos + 2) === CharacterCodes.slash) { - const textSubStr = currentText.substring(comment.pos, comment.end); - return fullTripleSlashReferencePathRegEx.test(textSubStr) - || fullTripleSlashAMDReferencePathRegEx.test(textSubStr); - } - return false; - } - - function getTrailingComments(range: TextRange): CommentRange[]; - function getTrailingComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (contextNode: Node) => boolean, getTextRangeCallback: (contextNode: Node) => TextRange): CommentRange[]; - function getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange) { - let ignored = false; - if (contextNode) { - if (ignoreNodeCallback(contextNode)) { - ignored = true; - } - else { - range = getTextRangeCallback(contextNode) || range; + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting leading comments and is an emitted node. + if (!skipTrailingComments && isEmittedNode) { + emitTrailingComments(end); } } - - let comments: CommentRange[]; - if (!ignored) { - comments = getTrailingCommentsOfPosition(range.end); - } - return comments; - } - - function getLeadingCommentsOfPosition(pos: number) { - if (positionIsSynthesized(pos) || leadingCommentRangePositions[pos]) { - return undefined; - } - - leadingCommentRangePositions[pos] = true; - const comments = hasDetachedComments(pos) - ? getLeadingCommentsWithoutDetachedComments() - : getLeadingCommentRanges(currentText, pos, consumedCommentRanges); - return comments; - } - - function getTrailingCommentsOfPosition(pos: number) { - if (positionIsSynthesized(pos) || trailingCommentRangePositions[pos]) { - return undefined; - } - - trailingCommentRangePositions[pos] = true; - const comments = getTrailingCommentRanges(currentText, pos, consumedCommentRanges); - return comments; - } - - function emitLeadingComments(range: TextRange, comments: CommentRange[]): void; - function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode: Node, getTextRangeCallback: (contextNode: Node) => TextRange): void; - function emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange) { - if (comments && comments.length > 0) { - if (contextNode) { - range = getTextRangeCallback(contextNode) || range; - } - - emitNewLineBeforeLeadingComments(currentLineMap, writer, range, comments); - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); - } - } - - function emitTrailingComments(range: TextRange, comments: CommentRange[]) { - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ - emitComments(currentText, currentLineMap, writer, comments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); - } - - function emitLeadingDetachedComments(range: TextRange): void; - function emitLeadingDetachedComments(range: TextRange, contextNode: Node, ignoreNodeCallback: (node: Node) => boolean): void; - function emitLeadingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean): void { - if (contextNode && ignoreNodeCallback(contextNode)) { - return; - } - - emitDetachedCommentsAndUpdateCommentsInfo(range, /*removeComments*/ false); - } - - function emitTrailingDetachedComments(range: TextRange): void; - function emitTrailingDetachedComments(range: TextRange, contextNode: Node, ignoreNodeCallback?: (node: Node) => boolean): void; - function emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (node: Node) => boolean): void { - if (contextNode && ignoreNodeCallback(contextNode)) { - return; - } - - range = collapseRangeToEnd(range); - emitLeadingComments(range, getLeadingComments(range)); } } - function createCommentWriterWithExtendedDiagnostics(writer: CommentWriter): CommentWriter { - const { - reset, - setSourceFile, - getLeadingComments, - getTrailingComments, - getTrailingCommentsOfPosition, - emitLeadingComments, - emitTrailingComments, - emitLeadingDetachedComments, - emitTrailingDetachedComments - } = writer; + function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + const { pos, end } = detachedRange; + const emitFlags = node.emitFlags; + const skipLeadingComments = pos < 0 || (emitFlags & NodeEmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = end < 0 || (emitFlags & NodeEmitFlags.NoTrailingComments) !== 0 || compilerOptions.removeComments; - return { - reset, - setSourceFile, - getLeadingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { - performance.mark("commentStart"); - const comments = getLeadingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); - return comments; - }, - getTrailingComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean, getTextRangeCallback?: (contextNode: Node) => TextRange): CommentRange[] { - performance.mark("commentStart"); - const comments = getTrailingComments(range, contextNode, ignoreNodeCallback, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); - return comments; - }, - getTrailingCommentsOfPosition(pos: number): CommentRange[] { - performance.mark("commentStart"); - const comments = getTrailingCommentsOfPosition(pos); - performance.measure("commentTime", "commentStart"); - return comments; - }, - emitLeadingComments(range: TextRange, comments: CommentRange[], contextNode?: Node, getTextRangeCallback?: (contextNode: Node) => TextRange): void { - performance.mark("commentStart"); - emitLeadingComments(range, comments, contextNode, getTextRangeCallback); - performance.measure("commentTime", "commentStart"); - }, - emitTrailingComments(range: TextRange, comments: CommentRange[]): void { - performance.mark("commentStart"); - emitLeadingComments(range, comments); - performance.measure("commentTime", "commentStart"); - }, - emitLeadingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { - performance.mark("commentStart"); - emitLeadingDetachedComments(range, contextNode, ignoreNodeCallback); - performance.measure("commentTime", "commentStart"); - }, - emitTrailingDetachedComments(range: TextRange, contextNode?: Node, ignoreNodeCallback?: (contextNode: Node) => boolean): void { - performance.mark("commentStart"); - emitTrailingDetachedComments(range, contextNode, ignoreNodeCallback); - performance.measure("commentTime", "commentStart"); + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange, compilerOptions.removeComments); + } + + emitCallback(node); + + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + } + } + + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + let leadingComments: CommentRange[]; + if (isEmittedNode) { + leadingComments = getLeadingCommentsToEmit(pos); + } + else { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + if (pos === 0) { + leadingComments = filter(getLeadingCommentsToEmit(pos), isTripleSlashComment); } - }; + } + + emitNewLineBeforeLeadingCommentsOfPosition(currentLineMap, writer, pos, leadingComments); + + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitComments(currentText, currentLineMap, writer, leadingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); + } + + function emitTrailingComments(pos: number) { + const trailingComments = getTrailingCommentsToEmit(pos); + + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment*/ + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ true, /*trailingSeparator*/ false, newLine, writeComment); + } + + function emitTrailingCommentsOfPosition(pos: number) { + if (compilerOptions.removeComments) { + return; + } + + const trailingComments = getTrailingCommentsToEmit(pos); + + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + emitComments(currentText, currentLineMap, writer, trailingComments, /*leadingSeparator*/ false, /*trailingSeparator*/ true, newLine, writeComment); + } + + function getLeadingCommentsToEmit(pos: number) { + // Emit the leading comments only if the container's pos doesn't match because the container should take care of emitting these comments + if (containerPos === -1 || pos !== containerPos) { + return hasDetachedComments(pos) + ? getLeadingCommentsWithoutDetachedComments() + : getLeadingCommentRanges(currentText, pos); + } + } + + function getTrailingCommentsToEmit(end: number) { + // Emit the trailing comments only if the container's end doesn't match because the container should take care of emitting these comments + if (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd)) { + return getTrailingCommentRanges(currentText, end); + } + } + + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + **/ + function isTripleSlashComment(comment: CommentRange) { + // Verify this is /// comment, but do the regexp match only when we first can find /// in the comment text + // so that we don't end up computing comment string and doing match for all // comments + if (currentText.charCodeAt(comment.pos + 1) === CharacterCodes.slash && + comment.pos + 2 < comment.end && + currentText.charCodeAt(comment.pos + 2) === CharacterCodes.slash) { + const textSubStr = currentText.substring(comment.pos, comment.end); + return textSubStr.match(fullTripleSlashReferencePathRegEx) || + textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ? + true : false; + } + return false; } function reset() { @@ -276,8 +194,6 @@ namespace ts { currentLineMap = undefined; detachedCommentsInfo = undefined; consumedCommentRanges = undefined; - trailingCommentRangePositions = undefined; - leadingCommentRangePositions = undefined; } function setSourceFile(sourceFile: SourceFile) { @@ -286,8 +202,6 @@ namespace ts { currentLineMap = getLineStarts(currentSourceFile); detachedCommentsInfo = undefined; consumedCommentRanges = {}; - leadingCommentRangePositions = {}; - trailingCommentRangePositions = {}; } function hasDetachedComments(pos: number) { @@ -297,7 +211,7 @@ namespace ts { function getLeadingCommentsWithoutDetachedComments() { // get the leading comments from detachedPos const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos; - const leadingComments = getLeadingCommentRanges(currentText, pos, consumedCommentRanges); + const leadingComments = getLeadingCommentRanges(currentText, pos); if (detachedCommentsInfo.length - 1) { detachedCommentsInfo.pop(); } diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 12d399b04d8..e730334515a 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -161,13 +161,9 @@ const _super = (function (geti, seti) { const comments = createCommentWriter(host, writer, sourceMap); const { - getLeadingComments, - getTrailingComments, - getTrailingCommentsOfPosition, - emitLeadingComments, - emitTrailingComments, - emitLeadingDetachedComments, - emitTrailingDetachedComments + emitNodeWithComments, + emitBodyWithDetachedComments, + emitTrailingCommentsOfPosition } = comments; let context: TransformationContext; @@ -175,7 +171,6 @@ const _super = (function (geti, seti) { let setNodeEmitFlags: (node: Node, flags: NodeEmitFlags) => void; let getSourceMapRange: (node: Node) => TextRange; let getTokenSourceMapRange: (node: Node, token: SyntaxKind) => TextRange; - let getCommentRange: (node: Node) => TextRange; let isSubstitutionEnabled: (node: Node) => boolean; let isEmitNotificationEnabled: (node: Node) => boolean; let onSubstituteNode: (node: Node, isExpression: boolean) => Node; @@ -240,7 +235,6 @@ const _super = (function (geti, seti) { setNodeEmitFlags = undefined; getSourceMapRange = undefined; getTokenSourceMapRange = undefined; - getCommentRange = undefined; isSubstitutionEnabled = undefined; isEmitNotificationEnabled = undefined; onSubstituteNode = undefined; @@ -262,7 +256,6 @@ const _super = (function (geti, seti) { setNodeEmitFlags = context.setNodeEmitFlags; getSourceMapRange = context.getSourceMapRange; getTokenSourceMapRange = context.getTokenSourceMapRange; - getCommentRange = context.getCommentRange; isSubstitutionEnabled = context.isSubstitutionEnabled; isEmitNotificationEnabled = context.isEmitNotificationEnabled; onSubstituteNode = context.onSubstituteNode; @@ -276,7 +269,7 @@ const _super = (function (geti, seti) { currentFileIdentifiers = node.identifiers; sourceMap.setSourceFile(node); comments.setSourceFile(node); - emitNodeWithNotificationOption(node, emitWorker); + emitNodeWithNotification(node, emitWorker); return node; } @@ -291,7 +284,7 @@ const _super = (function (geti, seti) { * Emits a node. */ function emit(node: Node) { - emitNodeWithNotificationOption(node, emitWithoutNotificationOption); + emitNodeWithNotification(node, emitWithComments); } /** @@ -315,51 +308,71 @@ const _super = (function (geti, seti) { } /** - * Emits a node without calling onEmitNode. - * NOTE: Do not call this method directly. + * Emits a node with comments. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from emit. */ - function emitWithoutNotificationOption(node: Node) { - emitNodeWithWorker(node, emitWorker); + function emitWithComments(node: Node) { + emitNodeWithComments(node, emitWithSourceMap); + } + + /** + * Emits a node with source maps. + * + * NOTE: Do not call this method directly. It is part of the emit pipeline + * and should only be called indirectly from emitWithComments. + */ + function emitWithSourceMap(node: Node) { + emitNodeWithSourceMap(node, emitWorker); } /** * Emits an expression node. */ function emitExpression(node: Expression) { - emitNodeWithNotificationOption(node, emitExpressionWithoutNotificationOption); + emitNodeWithNotification(node, emitExpressionWithComments); } /** - * Emits an expression without calling onEmitNode. - * NOTE: Do not call this method directly. + * Emits an expression with comments. + * + * NOTE: Do not call this method directly. It is part of the emitExpression pipeline + * and should only be called indirectly from emitExpression. */ - function emitExpressionWithoutNotificationOption(node: Expression) { - emitNodeWithWorker(node, emitExpressionWorker); + function emitExpressionWithComments(node: Expression) { + emitNodeWithComments(node, emitExpressionWithSourceMap); + } + + /** + * Emits an expression with source maps. + * + * NOTE: Do not call this method directly. It is part of the emitExpression pipeline + * and should only be called indirectly from emitExpressionWithComments. + */ + function emitExpressionWithSourceMap(node: Expression) { + emitNodeWithSourceMap(node, emitExpressionWorker); } /** * Emits a node with emit notification if available. */ - function emitNodeWithNotificationOption(node: Node, emit: (node: Node) => void) { + function emitNodeWithNotification(node: Node, emitCallback: (node: Node) => void) { if (node) { if (isEmitNotificationEnabled(node)) { - onEmitNode(node, emit); + onEmitNode(node, emitCallback); } else { - emit(node); + emitCallback(node); } } } - function emitNodeWithWorker(node: Node, emitWorker: (node: Node) => void) { + function emitNodeWithSourceMap(node: Node, emitCallback: (node: Node) => void) { if (node) { - const leadingComments = getLeadingComments(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingCommentsForNode, getCommentRange); - const trailingComments = getTrailingComments(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingCommentsForNode, getCommentRange); - emitLeadingComments(/*range*/ node, leadingComments, /*contextNode*/ node, getCommentRange); emitStart(/*range*/ node, /*contextNode*/ node, shouldSkipLeadingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - emitWorker(node); + emitCallback(node); emitEnd(/*range*/ node, /*contextNode*/ node, shouldSkipTrailingSourceMapForNode, shouldSkipSourceMapForChildren, getSourceMapRange); - emitTrailingComments(node, trailingComments); } } @@ -1642,13 +1655,26 @@ const _super = (function (geti, seti) { } increaseIndent(); - emitLeadingDetachedComments(body.statements, body, shouldSkipLeadingCommentsForNode); + emitBodyWithDetachedComments(body, body.statements, + shouldEmitBlockFunctionBodyOnSingleLine(parentNode, body) + ? emitBlockFunctionBodyOnSingleLine + : emitBlockFunctionBodyWorker); + + decreaseIndent(); + writeToken(SyntaxKind.CloseBraceToken, body.statements.end, body); + } + + function emitBlockFunctionBodyOnSingleLine(body: Block) { + emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); + } + + function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { // Emit all the prologue directives (like "use strict"). const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); const helpersEmitted = emitHelpers(body); - if (statementOffset === 0 && !helpersEmitted && shouldEmitBlockFunctionBodyOnSingleLine(parentNode, body)) { + if (statementOffset === 0 && !helpersEmitted && emitBlockFunctionBodyOnSingleLine) { decreaseIndent(); emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); increaseIndent(); @@ -1656,10 +1682,6 @@ const _super = (function (geti, seti) { else { emitList(body, body.statements, ListFormat.MultiLineFunctionBodyStatements, statementOffset); } - - emitTrailingDetachedComments(body.statements, body, shouldSkipTrailingCommentsForNode); - decreaseIndent(); - writeToken(SyntaxKind.CloseBraceToken, body.statements.end, body); } function emitClassDeclaration(node: ClassDeclaration) { @@ -2004,9 +2026,12 @@ const _super = (function (geti, seti) { // } // "comment1" is not considered to be leading comment for node.initializer // but rather a trailing comment on the previous node. - if (!shouldSkipLeadingCommentsForNode(node.initializer)) { - emitLeadingComments(/*range*/ node.initializer, getTrailingComments(collapseRangeToStart(node.initializer)), /*contextNode*/ node.initializer, getCommentRange); + const initializer = node.initializer; + if (!shouldSkipLeadingCommentsForNode(initializer)) { + const commentRange = initializer.commentRange || initializer; + emitTrailingCommentsOfPosition(commentRange.pos); } + emitExpression(node.initializer); } @@ -2034,8 +2059,10 @@ const _super = (function (geti, seti) { function emitSourceFile(node: SourceFile) { writeLine(); emitShebang(); - emitLeadingDetachedComments(node); + emitBodyWithDetachedComments(node, node.statements, emitSourceFileWorker); + } + function emitSourceFileWorker(node: SourceFile) { const statements = node.statements; const statementOffset = emitPrologueDirectives(statements); if (getNodeEmitFlags(node) & NodeEmitFlags.NoLexicalEnvironment) { @@ -2049,8 +2076,6 @@ const _super = (function (geti, seti) { emitList(node, statements, ListFormat.MultiLine, statementOffset); tempFlags = savedTempFlags; } - - emitTrailingDetachedComments(node.statements); } // Transformation nodes @@ -2326,7 +2351,8 @@ const _super = (function (geti, seti) { } else { // Write the opening line terminator or leading whitespace. - let shouldEmitInterveningComments = true; + const mayEmitInterveningComments = (format & ListFormat.NoInterveningComments) === 0; + let shouldEmitInterveningComments = mayEmitInterveningComments; if (shouldWriteLeadingLineTerminator(parentNode, children, format)) { writeLine(); shouldEmitInterveningComments = false; @@ -2369,10 +2395,11 @@ const _super = (function (geti, seti) { } if (shouldEmitInterveningComments) { - emitLeadingComments(/*node*/ child, getTrailingCommentsOfPosition(child.pos), /*contextNode*/ child, getCommentRange); + const commentRange = child.commentRange || child; + emitTrailingCommentsOfPosition(commentRange.pos); } else { - shouldEmitInterveningComments = true; + shouldEmitInterveningComments = mayEmitInterveningComments; } // Emit this child. @@ -2876,6 +2903,7 @@ const _super = (function (geti, seti) { // Other PreferNewLine = 1 << 15, // Prefer adding a LineTerminator between synthesized nodes. NoTrailingNewLine = 1 << 16, // Do not emit a trailing NewLine for a MultiLine list. + NoInterveningComments = 1 << 17,// Do not emit comments between each node // Precomputed Formats Modifiers = SingleLine | SpaceBetweenSiblings, @@ -2890,7 +2918,7 @@ const _super = (function (geti, seti) { ArrayLiteralExpressionElements = PreserveLines | CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | Indented | SquareBrackets, CallExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis, NewExpressionArguments = CommaDelimited | SpaceBetweenSiblings | SingleLine | Parenthesis | OptionalIfUndefined, - TemplateExpressionSpans = SingleLine, + TemplateExpressionSpans = SingleLine | NoInterveningComments, SingleLineBlockStatements = SpaceBetweenBraces | SpaceBetweenSiblings | SingleLine, MultiLineBlockStatements = Indented | MultiLine, VariableDeclarationList = CommaDelimited | SpaceBetweenSiblings | SingleLine, @@ -2902,8 +2930,8 @@ const _super = (function (geti, seti) { EnumMembers = CommaDelimited | Indented | MultiLine, CaseBlockClauses = Indented | MultiLine, NamedImportsOrExportsElements = CommaDelimited | SpaceBetweenSiblings | AllowTrailingComma | SingleLine | SpaceBetweenBraces, - JsxElementChildren = SingleLine, - JsxElementAttributes = SingleLine | SpaceBetweenSiblings, + JsxElementChildren = SingleLine | NoInterveningComments, + JsxElementAttributes = SingleLine | SpaceBetweenSiblings | NoInterveningComments, CaseOrDefaultClauseStatements = Indented | MultiLine | NoTrailingNewLine | OptionalIfEmpty, HeritageClauseTypes = CommaDelimited | SpaceBetweenSiblings | SingleLine, SourceFileStatements = MultiLine | NoTrailingNewLine, diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 254923a981e..bbadd63e1f0 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -591,7 +591,7 @@ namespace ts { * and the next token are returned. * If true, comments occurring between the given position and the next line break are returned. */ - function getCommentRanges(text: string, pos: number, trailing: boolean, consumedCommentRanges?: Map): CommentRange[] { + function getCommentRanges(text: string, pos: number, trailing: boolean): CommentRange[] { let result: CommentRange[]; let collecting = trailing || pos === 0; while (pos >= 0 && pos < text.length) { @@ -643,15 +643,12 @@ namespace ts { } } - if (collecting && (!consumedCommentRanges || !(startPos in consumedCommentRanges))) { + if (collecting) { if (!result) { result = []; } result.push({ pos: startPos, end: pos, hasTrailingNewLine, kind }); - if (consumedCommentRanges) { - consumedCommentRanges[startPos] = true; - } } continue; @@ -673,12 +670,12 @@ namespace ts { return result; } - export function getLeadingCommentRanges(text: string, pos: number, consumedCommentRanges?: Map): CommentRange[] { - return getCommentRanges(text, pos, /*trailing*/ false, consumedCommentRanges); + export function getLeadingCommentRanges(text: string, pos: number): CommentRange[] { + return getCommentRanges(text, pos, /*trailing*/ false); } - export function getTrailingCommentRanges(text: string, pos: number, consumedCommentRanges?: Map): CommentRange[] { - return getCommentRanges(text, pos, /*trailing*/ true, consumedCommentRanges); + export function getTrailingCommentRanges(text: string, pos: number): CommentRange[] { + return getCommentRanges(text, pos, /*trailing*/ true); } /** Optionally, get the shebang */ diff --git a/src/compiler/transformers/es6.ts b/src/compiler/transformers/es6.ts index 16d08298f72..b796a3a7596 100644 --- a/src/compiler/transformers/es6.ts +++ b/src/compiler/transformers/es6.ts @@ -1526,6 +1526,7 @@ namespace ts { const declarationList = createVariableDeclarationList(declarations, /*location*/ node); setOriginalNode(declarationList, node); + setCommentRange(declarationList, node); if (node.transformFlags & TransformFlags.ContainsBindingPattern && (isBindingPattern(node.declarations[0].name) diff --git a/src/compiler/transformers/module/module.ts b/src/compiler/transformers/module/module.ts index 121365dd84c..af214a608cd 100644 --- a/src/compiler/transformers/module/module.ts +++ b/src/compiler/transformers/module/module.ts @@ -17,6 +17,7 @@ namespace ts { hoistVariableDeclaration, setNodeEmitFlags, getNodeEmitFlags, + setSourceMapRange, } = context; const compilerOptions = context.getCompilerOptions(); @@ -926,7 +927,13 @@ namespace ts { } function createExportStatement(name: Identifier, value: Expression, location?: TextRange) { - return startOnNewLine(createStatement(createExportAssignment(name, value), location)); + const statement = createStatement(createExportAssignment(name, value)); + statement.startsOnNewLine = true; + if (location) { + setSourceMapRange(statement, location); + } + + return statement; } function createExportAssignment(name: Identifier, value: Expression) { diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index e0ad54eb377..d7998e65a0d 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -2376,17 +2376,16 @@ namespace ts { * Adds a trailing VariableStatement for an enum or module declaration. */ function addVarForEnumExportedFromNamespace(statements: Statement[], node: EnumDeclaration | ModuleDeclaration) { - statements.push( - createVariableStatement( - /*modifiers*/ undefined, - [createVariableDeclaration( - getDeclarationName(node), - /*type*/ undefined, - getExportName(node) - )], - /*location*/ node - ) + const statement = createVariableStatement( + /*modifiers*/ undefined, + [createVariableDeclaration( + getDeclarationName(node), + /*type*/ undefined, + getExportName(node) + )] ); + setSourceMapRange(statement, node); + statements.push(statement); } /** @@ -2402,48 +2401,62 @@ namespace ts { } const statements: Statement[] = []; + + // We request to be advised when the printer is about to print this node. This allows + // us to set up the correct state for later substitutions. + let emitFlags = NodeEmitFlags.AdviseOnEmitNode; + + // If needed, we should emit a variable declaration for the enum. If we emit + // a leading variable declaration, we should not emit leading comments for the + // enum body. if (shouldEmitVarForEnumDeclaration(node)) { addVarForEnumOrModuleDeclaration(statements, node); + + // We should still emit the comments if we are emitting a system module. + if (moduleKind !== ModuleKind.System || currentScope !== currentSourceFile) { + emitFlags |= NodeEmitFlags.NoLeadingComments; + } } - const innerName = getNamespaceContainerName(node); - const paramName = getNamespaceParameterName(node); + // `parameterName` is the declaration name used inside of the enum. + const parameterName = getNamespaceParameterName(node); + + // `containerName` is the expression used inside of the enum for assignments. + const containerName = getNamespaceContainerName(node); + + // `exportName` is the expression used within this node's container for any exported references. const exportName = getExportName(node); // (function (x) { // x[x["y"] = 0] = "y"; // ... // })(x || (x = {})); - statements.push( - setNodeEmitFlags( - setOriginalNode( - createStatement( - createCall( - createFunctionExpression( - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - [createParameter(paramName)], - /*type*/ undefined, - transformEnumBody(node, innerName) - ), - /*typeArguments*/ undefined, - [createLogicalOr( - exportName, - createAssignment( - exportName, - createObjectLiteral() - ) - )] - ), - /*location*/ node - ), - /*original*/ node + const enumStatement = createStatement( + createCall( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [createParameter(parameterName)], + /*type*/ undefined, + transformEnumBody(node, containerName) ), - NodeEmitFlags.AdviseOnEmitNode - ) + /*typeArguments*/ undefined, + [createLogicalOr( + exportName, + createAssignment( + exportName, + createObjectLiteral() + ) + )] + ), + /*location*/ node ); + setOriginalNode(enumStatement, node); + setNodeEmitFlags(enumStatement, emitFlags); + statements.push(enumStatement); + if (isNamespaceExport(node)) { addVarForEnumExportedFromNamespace(statements, node); } @@ -2614,8 +2627,19 @@ namespace ts { const statements: Statement[] = []; + // We request to be advised when the printer is about to print this node. This allows + // us to set up the correct state for later substitutions. + let emitFlags = NodeEmitFlags.AdviseOnEmitNode; + + // If needed, we should emit a variable declaration for the module. If we emit + // a leading variable declaration, we should not emit leading comments for the + // module body. if (shouldEmitVarForModuleDeclaration(node)) { addVarForEnumOrModuleDeclaration(statements, node); + // We should still emit the comments if we are emitting a system module. + if (moduleKind !== ModuleKind.System || currentScope !== currentSourceFile) { + emitFlags |= NodeEmitFlags.NoLeadingComments; + } } // `parameterName` is the declaration name used inside of the namespace. @@ -2649,30 +2673,25 @@ namespace ts { // (function (x_1) { // x_1.y = ...; // })(x || (x = {})); - statements.push( - setNodeEmitFlags( - setOriginalNode( - createStatement( - createCall( - createFunctionExpression( - /*asteriskToken*/ undefined, - /*name*/ undefined, - /*typeParameters*/ undefined, - [createParameter(parameterName)], - /*type*/ undefined, - transformModuleBody(node, containerName) - ), - /*typeArguments*/ undefined, - [moduleArg] - ), - /*location*/ node - ), - /*original*/ node + const moduleStatement = createStatement( + createCall( + createFunctionExpression( + /*asteriskToken*/ undefined, + /*name*/ undefined, + /*typeParameters*/ undefined, + [createParameter(parameterName)], + /*type*/ undefined, + transformModuleBody(node, containerName) ), - NodeEmitFlags.AdviseOnEmitNode - ) + /*typeArguments*/ undefined, + [moduleArg] + ), + /*location*/ node ); + setOriginalNode(moduleStatement, node); + setNodeEmitFlags(moduleStatement, emitFlags); + statements.push(moduleStatement); return statements; } @@ -2846,16 +2865,15 @@ namespace ts { } function addExportMemberAssignment(statements: Statement[], node: DeclarationStatement) { - statements.push( - createStatement( - createAssignment( - getExportName(node), - getLocalName(node, /*noSourceMaps*/ true), - /*location*/ createRange(node.name.pos, node.end) - ), - /*location*/ createRange(-1, node.end) - ) + const expression = createAssignment( + getExportName(node), + getLocalName(node, /*noSourceMaps*/ true) ); + setSourceMapRange(expression, createRange(node.name.pos, node.end)); + + const statement = createStatement(expression); + setSourceMapRange(statement, createRange(-1, node.end)); + statements.push(statement); } function createNamespaceExport(exportName: Identifier, exportValue: Expression, location?: TextRange) { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index c4a644dcc1d..7ecb0fa194b 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -695,6 +695,7 @@ namespace ts { export let fullTripleSlashReferenceTypeReferenceDirectiveRegEx = /^(\/\/\/\s*/; export let fullTripleSlashAMDReferencePathRegEx = /^(\/\/\/\s*/; + export function isPartOfTypeNode(node: Node): boolean { if (SyntaxKind.FirstTypeNode <= node.kind && node.kind <= SyntaxKind.LastTypeNode) { return true; @@ -2590,9 +2591,13 @@ namespace ts { } export function emitNewLineBeforeLeadingComments(lineMap: number[], writer: EmitTextWriter, node: TextRange, leadingComments: CommentRange[]) { + emitNewLineBeforeLeadingCommentsOfPosition(lineMap, writer, node.pos, leadingComments); + } + + export function emitNewLineBeforeLeadingCommentsOfPosition(lineMap: number[], writer: EmitTextWriter, pos: number, leadingComments: CommentRange[]) { // If the leading comments start on different line than the start of node, write new line - if (leadingComments && leadingComments.length && node.pos !== leadingComments[0].pos && - getLineOfLocalPositionFromLineMap(lineMap, node.pos) !== getLineOfLocalPositionFromLineMap(lineMap, leadingComments[0].pos)) { + if (leadingComments && leadingComments.length && pos !== leadingComments[0].pos && + getLineOfLocalPositionFromLineMap(lineMap, pos) !== getLineOfLocalPositionFromLineMap(lineMap, leadingComments[0].pos)) { writer.writeLine(); } }