diff --git a/Jakefile.js b/Jakefile.js index 282471a09ef..26e9ef4ab01 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -82,70 +82,7 @@ var cancellationTokenSources = filesFromConfig(path.join(serverDirectory, "cance var typingsInstallerSources = filesFromConfig(path.join(serverDirectory, "typingsInstaller/tsconfig.json")); var serverSources = filesFromConfig(path.join(serverDirectory, "tsconfig.json")) var languageServiceLibrarySources = filesFromConfig(path.join(serverDirectory, "tsconfig.library.json")); - -var harnessCoreSources = [ - "harness.ts", - "virtualFileSystem.ts", - "sourceMapRecorder.ts", - "harnessLanguageService.ts", - "fourslash.ts", - "runnerbase.ts", - "compilerRunner.ts", - "typeWriter.ts", - "fourslashRunner.ts", - "projectsRunner.ts", - "loggedIO.ts", - "rwcRunner.ts", - "test262Runner.ts", - "runner.ts" -].map(function (f) { - return path.join(harnessDirectory, f); -}); - -var harnessSources = harnessCoreSources.concat([ - "incrementalParser.ts", - "jsDocParsing.ts", - "services/colorization.ts", - "services/documentRegistry.ts", - "services/preProcessFile.ts", - "services/patternMatcher.ts", - "session.ts", - "versionCache.ts", - "convertToBase64.ts", - "transpile.ts", - "reuseProgramStructure.ts", - "textStorage.ts", - "cachingInServerLSHost.ts", - "moduleResolution.ts", - "tsconfigParsing.ts", - "commandLineParsing.ts", - "configurationExtension.ts", - "convertCompilerOptionsFromJson.ts", - "convertTypeAcquisitionFromJson.ts", - "tsserverProjectSystem.ts", - "compileOnSave.ts", - "typingsInstaller.ts", - "projectErrors.ts", - "matchFiles.ts", - "initializeTSConfig.ts", - "printer.ts", - "transform.ts", - "customTransforms.ts", -].map(function (f) { - return path.join(unittestsDirectory, f); -})).concat([ - "protocol.ts", - "utilities.ts", - "scriptVersionCache.ts", - "scriptInfo.ts", - "lsHost.ts", - "project.ts", - "typingsCache.ts", - "editorServices.ts", - "session.ts", -].map(function (f) { - return path.join(serverDirectory, f); -})); +var harnessSources = filesFromConfig("./src/harness/tsconfig.json"); var es2015LibrarySources = [ "es2015.core.d.ts", diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts deleted file mode 100644 index 9e077ebfaa8..00000000000 --- a/src/compiler/comments.ts +++ /dev/null @@ -1,429 +0,0 @@ -/// - -/* @internal */ -namespace ts { - export interface CommentWriter { - reset(): void; - setSourceFile(sourceFile: SourceFile): void; - setWriter(writer: EmitTextWriter): void; - emitNodeWithComments(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; - emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void; - emitTrailingCommentsOfPosition(pos: number): void; - emitLeadingCommentsOfPosition(pos: number): void; - } - - export function createCommentWriter(printerOptions: PrinterOptions, emitPos: ((pos: number) => void) | undefined): CommentWriter { - const extendedDiagnostics = printerOptions.extendedDiagnostics; - const newLine = getNewLineCharacter(printerOptions); - let writer: EmitTextWriter; - let containerPos = -1; - let containerEnd = -1; - let declarationListContainerEnd = -1; - let currentSourceFile: SourceFile; - let currentText: string; - let currentLineMap: number[]; - let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; - let hasWrittenComment = false; - let disabled: boolean = printerOptions.removeComments; - - return { - reset, - setWriter, - setSourceFile, - emitNodeWithComments, - emitBodyWithDetachedComments, - emitTrailingCommentsOfPosition, - emitLeadingCommentsOfPosition, - }; - - function emitNodeWithComments(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - if (disabled) { - emitCallback(hint, node); - return; - } - - if (node) { - hasWrittenComment = false; - - const emitNode = node.emitNode; - const emitFlags = emitNode && emitNode.flags; - const { pos, end } = emitNode && emitNode.commentRange || node; - if ((pos < 0 && end < 0) || (pos === end)) { - // Both pos and end are synthesized, so just emit the node without comments. - emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback); - } - else { - if (extendedDiagnostics) { - performance.mark("preEmitNodeWithComment"); - } - - const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement; - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - - // 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 (extendedDiagnostics) { - performance.measure("commentTime", "preEmitNodeWithComment"); - } - - emitNodeWithSynthesizedComments(hint, node, emitNode, emitFlags, emitCallback); - - if (extendedDiagnostics) { - performance.mark("postEmitNodeWithComment"); - } - - // Restore previous container state. - containerPos = savedContainerPos; - containerEnd = savedContainerEnd; - declarationListContainerEnd = savedDeclarationListContainerEnd; - - // 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); - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "postEmitNodeWithComment"); - } - } - } - } - - function emitNodeWithSynthesizedComments(hint: EmitHint, node: Node, emitNode: EmitNode, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { - const leadingComments = emitNode && emitNode.leadingComments; - if (some(leadingComments)) { - if (extendedDiagnostics) { - performance.mark("preEmitNodeWithSynthesizedComments"); - } - - forEach(leadingComments, emitLeadingSynthesizedComment); - - if (extendedDiagnostics) { - performance.measure("commentTime", "preEmitNodeWithSynthesizedComments"); - } - } - - emitNodeWithNestedComments(hint, node, emitFlags, emitCallback); - - const trailingComments = emitNode && emitNode.trailingComments; - if (some(trailingComments)) { - if (extendedDiagnostics) { - performance.mark("postEmitNodeWithSynthesizedComments"); - } - - forEach(trailingComments, emitTrailingSynthesizedComment); - - if (extendedDiagnostics) { - performance.measure("commentTime", "postEmitNodeWithSynthesizedComments"); - } - } - } - - function emitLeadingSynthesizedComment(comment: SynthesizedComment) { - if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { - writer.writeLine(); - } - else { - writer.write(" "); - } - } - - function emitTrailingSynthesizedComment(comment: SynthesizedComment) { - if (!writer.isAtStartOfLine()) { - writer.write(" "); - } - writeSynthesizedComment(comment); - if (comment.hasTrailingNewLine) { - writer.writeLine(); - } - } - - function writeSynthesizedComment(comment: SynthesizedComment) { - const text = formatSynthesizedComment(comment); - const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; - writeCommentRange(text, lineMap, writer, 0, text.length, newLine); - } - - function formatSynthesizedComment(comment: SynthesizedComment) { - return comment.kind === SyntaxKind.MultiLineCommentTrivia - ? `/*${comment.text}*/` - : `//${comment.text}`; - } - - function emitNodeWithNestedComments(hint: EmitHint, node: Node, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { - if (emitFlags & EmitFlags.NoNestedComments) { - disabled = true; - emitCallback(hint, node); - disabled = false; - } - else { - emitCallback(hint, node); - } - } - - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - if (extendedDiagnostics) { - performance.mark("preEmitBodyWithDetachedComments"); - } - - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = disabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - - if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "preEmitBodyWithDetachedComments"); - } - - if (emitFlags & EmitFlags.NoNestedComments && !disabled) { - disabled = true; - emitCallback(node); - disabled = false; - } - else { - emitCallback(node); - } - - if (extendedDiagnostics) { - performance.mark("beginEmitBodyWithDetachedCommetns"); - } - - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "beginEmitBodyWithDetachedCommetns"); - } - } - - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; - - if (isEmittedNode) { - forEachLeadingCommentToEmit(pos, emitLeadingComment); - } - else if (pos === 0) { - // 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 - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); - } - } - - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } - } - - function emitLeadingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!hasWrittenComment) { - emitNewLineBeforeLeadingCommentOfPosition(currentLineMap, writer, rangePos, commentPos); - hasWrittenComment = true; - } - - // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space - if (emitPos) emitPos(commentPos); - writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); - if (emitPos) emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - else { - writer.write(" "); - } - } - - function emitLeadingCommentsOfPosition(pos: number) { - if (disabled || pos === -1) { - return; - } - - emitLeadingComments(pos, /*isEmittedNode*/ true); - } - - function emitTrailingComments(pos: number) { - forEachTrailingCommentToEmit(pos, emitTrailingComment); - } - - function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ - if (!writer.isAtStartOfLine()) { - writer.write(" "); - } - - if (emitPos) emitPos(commentPos); - writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); - if (emitPos) emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - } - - function emitTrailingCommentsOfPosition(pos: number) { - if (disabled) { - return; - } - - if (extendedDiagnostics) { - performance.mark("beforeEmitTrailingCommentsOfPosition"); - } - - forEachTrailingCommentToEmit(pos, emitTrailingCommentOfPosition); - - if (extendedDiagnostics) { - performance.measure("commentTime", "beforeEmitTrailingCommentsOfPosition"); - } - } - - function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { - // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space - - if (emitPos) emitPos(commentPos); - writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); - if (emitPos) emitPos(commentEnd); - - if (hasTrailingNewLine) { - writer.writeLine(); - } - else { - writer.write(" "); - } - } - - function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // 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) { - if (hasDetachedComments(pos)) { - forEachLeadingCommentWithoutDetachedComments(cb); - } - else { - forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos); - } - } - } - - function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { - // 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)) { - forEachTrailingCommentRange(currentText, end, cb); - } - } - - function reset() { - currentSourceFile = undefined; - currentText = undefined; - currentLineMap = undefined; - detachedCommentsInfo = undefined; - } - - function setWriter(output: EmitTextWriter): void { - writer = output; - } - - function setSourceFile(sourceFile: SourceFile) { - currentSourceFile = sourceFile; - currentText = currentSourceFile.text; - currentLineMap = getLineStarts(currentSourceFile); - detachedCommentsInfo = undefined; - } - - function hasDetachedComments(pos: number) { - return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos; - } - - function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { - // get the leading comments from detachedPos - const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos; - if (detachedCommentsInfo.length - 1) { - detachedCommentsInfo.pop(); - } - else { - detachedCommentsInfo = undefined; - } - - forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos); - } - - function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { - const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, range, newLine, disabled); - if (currentDetachedCommentInfo) { - if (detachedCommentsInfo) { - detachedCommentsInfo.push(currentDetachedCommentInfo); - } - else { - detachedCommentsInfo = [currentDetachedCommentInfo]; - } - } - } - - function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { - if (emitPos) emitPos(commentPos); - writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); - if (emitPos) emitPos(commentEnd); - } - - /** - * Determine if the given comment is a triple-slash - * - * @return true if the comment is a triple-slash comment else false - **/ - function isTripleSlashComment(commentPos: number, commentEnd: number) { - // 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(commentPos + 1) === CharacterCodes.slash && - commentPos + 2 < commentEnd && - currentText.charCodeAt(commentPos + 2) === CharacterCodes.slash) { - const textSubStr = currentText.substring(commentPos, commentEnd); - return textSubStr.match(fullTripleSlashReferencePathRegEx) || - textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ? - true : false; - } - return false; - } - } -} \ No newline at end of file diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index af8dbb07cbe..3add4c7903d 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -2,7 +2,6 @@ /// /// /// -/// namespace ts { const delimiters = createDelimiterMap(); @@ -194,24 +193,19 @@ namespace ts { hasGlobalName, onEmitSourceMapOfNode, onEmitSourceMapOfToken, - onEmitSourceMapOfPosition, + onEmitSourceMapOfPosition: emitPos, onEmitNode, onEmitHelpers, onSetSourceFile, substituteNode, } = handlers; + const extendedDiagnostics = printerOptions.extendedDiagnostics; const newLine = getNewLineCharacter(printerOptions); const languageVersion = getEmitScriptTarget(printerOptions); - const comments = createCommentWriter(printerOptions, onEmitSourceMapOfPosition); - const { - emitNodeWithComments, - emitBodyWithDetachedComments, - emitTrailingCommentsOfPosition, - emitLeadingCommentsOfPosition, - } = comments; let currentSourceFile: SourceFile; + let currentText: string; let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. let autoGeneratedIdToGeneratedName: string[]; // Map of generated names for temp and loop variables. let generatedNames: Map; // Set of names generated by the NameGenerator. @@ -219,6 +213,13 @@ namespace ts { let tempFlags: TempFlags; // TempFlags for the current name generation scope. let writer: EmitTextWriter; let ownWriter: EmitTextWriter; + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; + let currentLineMap: number[]; + let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[]; + let hasWrittenComment = false; + let disabled: boolean = printerOptions.removeComments; reset(); return { @@ -302,12 +303,14 @@ namespace ts { function print(hint: EmitHint, node: Node, sourceFile: SourceFile) { setSourceFile(sourceFile); - pipelineEmitWithNotification(hint, node); + pipelineEmit(hint, node); } function setSourceFile(sourceFile: SourceFile) { currentSourceFile = sourceFile; - comments.setSourceFile(sourceFile); + currentText = currentSourceFile.text; + currentLineMap = getLineStarts(currentSourceFile); + detachedCommentsInfo = undefined; if (onSetSourceFile) { onSetSourceFile(sourceFile); } @@ -315,57 +318,73 @@ namespace ts { function setWriter(output: EmitTextWriter | undefined) { writer = output; - comments.setWriter(output); } function reset() { + setWriter(/*output*/ undefined); nodeIdToGeneratedName = []; autoGeneratedIdToGeneratedName = []; generatedNames = createMap(); tempFlagsStack = []; tempFlags = TempFlags.Auto; - comments.reset(); - setWriter(/*output*/ undefined); + currentSourceFile = undefined; + currentText = undefined; + currentLineMap = undefined; + detachedCommentsInfo = undefined; } function emit(node: Node) { - pipelineEmitWithNotification(EmitHint.Unspecified, node); + pipelineEmit(EmitHint.Unspecified, node); } function emitIdentifierName(node: Identifier) { - pipelineEmitWithNotification(EmitHint.IdentifierName, node); + pipelineEmit(EmitHint.IdentifierName, node); } function emitExpression(node: Expression) { - pipelineEmitWithNotification(EmitHint.Expression, node); + pipelineEmit(EmitHint.Expression, node); + } + + function pipelineEmit(hint: EmitHint, node: Node) { + getInitialPhase(hint)(hint, trySubstituteNode(hint, node)); + } + + function getInitialPhase(hint: EmitHint) { + return !shouldSkipNotificationPhase() ? pipelineEmitWithNotification : getNextPhaseFollowingNotification(hint); + } + + function shouldSkipNotificationPhase() { + return onEmitNode === undefined; + } + + function getNextPhaseFollowingNotification(hint: EmitHint) { + return !shouldSkipCommentPhase(hint) ? emitNodeConsideringComments : getNextPhaseFollowingComments(hint); + } + + function shouldSkipCommentPhase(hint: EmitHint) { + return disabled || hint === EmitHint.SourceFile; + } + + function getNextPhaseFollowingComments(hint: EmitHint) { + return !shouldSkipSourceMapPhase(hint) ? pipelineEmitWithSourceMap : getNextPhaseFollowingSourceMap(); + } + + function shouldSkipSourceMapPhase(hint: EmitHint) { + return onEmitSourceMapOfNode === undefined + || hint === EmitHint.SourceFile + || hint === EmitHint.IdentifierName; + } + + function getNextPhaseFollowingSourceMap() { + return pipelineEmitWithHint; } function pipelineEmitWithNotification(hint: EmitHint, node: Node) { - if (onEmitNode) { - onEmitNode(hint, node, pipelineEmitWithComments); - } - else { - pipelineEmitWithComments(hint, node); - } - } - - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - node = trySubstituteNode(hint, node); - if (emitNodeWithComments && hint !== EmitHint.SourceFile) { - emitNodeWithComments(hint, node, pipelineEmitWithSourceMap); - } - else { - pipelineEmitWithSourceMap(hint, node); - } + onEmitNode(hint, node, getNextPhaseFollowingNotification(hint)); } function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { - if (onEmitSourceMapOfNode && hint !== EmitHint.SourceFile && hint !== EmitHint.IdentifierName) { - onEmitSourceMapOfNode(hint, node, pipelineEmitWithHint); - } - else { - pipelineEmitWithHint(hint, node); - } + onEmitSourceMapOfNode(hint, node, getNextPhaseFollowingSourceMap()); } function pipelineEmitWithHint(hint: EmitHint, node: Node): void { @@ -728,15 +747,6 @@ namespace ts { return node && substituteNode && substituteNode(hint, node) || node; } - function emitBodyIndirect(node: Node, elements: NodeArray, emitCallback: (node: Node) => void): void { - if (emitBodyWithDetachedComments) { - emitBodyWithDetachedComments(node, elements, emitCallback); - } - else { - emitCallback(node); - } - } - function emitHelpersIndirect(node: Node) { if (onEmitHelpers) { onEmitHelpers(node, writeLines); @@ -1635,26 +1645,30 @@ namespace ts { write(" {"); increaseIndent(); - const emitBlockFunctionBody = shouldEmitBlockFunctionBodyOnSingleLine(body) - ? emitBlockFunctionBodyOnSingleLine - : emitBlockFunctionBodyWorker; + const commentFlags = getEmitFlags(body); + emitLeadingDetachedComments(body.statements, commentFlags); - emitBodyIndirect(body, body.statements, emitBlockFunctionBody); + if (!disabled && (commentFlags & EmitFlags.NoNestedComments)) { + disabled = true; + emitBlockFunctionBodyWorker(body); + disabled = false; + } + else { + emitBlockFunctionBodyWorker(body); + } + + emitTrailingDetachedComments(body.statements, commentFlags); decreaseIndent(); writeToken(SyntaxKind.CloseBraceToken, body.statements.end, body); } - function emitBlockFunctionBodyOnSingleLine(body: Block) { - emitBlockFunctionBodyWorker(body, /*emitBlockFunctionBodyOnSingleLine*/ true); - } - - function emitBlockFunctionBodyWorker(body: Block, emitBlockFunctionBodyOnSingleLine?: boolean) { + function emitBlockFunctionBodyWorker(body: Block) { // Emit all the prologue directives (like "use strict"). - const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); const pos = writer.getTextPos(); + const statementOffset = emitPrologueDirectives(body.statements, /*startWithNewLine*/ true); emitHelpersIndirect(body); - if (statementOffset === 0 && pos === writer.getTextPos() && emitBlockFunctionBodyOnSingleLine) { + if (statementOffset === 0 && pos === writer.getTextPos() && shouldEmitBlockFunctionBodyOnSingleLine(body)) { decreaseIndent(); emitList(body, body.statements, ListFormat.SingleLineFunctionBodyStatements); increaseIndent(); @@ -2040,16 +2054,17 @@ namespace ts { function emitSourceFile(node: SourceFile) { writeLine(); emitShebang(); - emitBodyIndirect(node, node.statements, emitSourceFileWorker); - } - function emitSourceFileWorker(node: SourceFile) { - const statements = node.statements; - const statementOffset = emitPrologueDirectives(statements); + const commentFlags = getEmitFlags(node); + emitLeadingDetachedComments(node.statements, commentFlags); + + const statementOffset = emitPrologueDirectives(node.statements); pushNameGenerationScope(); emitHelpersIndirect(node); - emitList(node, statements, ListFormat.MultiLine, statementOffset); + emitList(node, node.statements, ListFormat.MultiLine, statementOffset); popNameGenerationScope(); + + emitTrailingDetachedComments(node.statements, commentFlags); } // Transformation nodes @@ -2493,9 +2508,9 @@ namespace ts { } function needsIndentation(parent: Node, node1: Node, node2: Node): boolean { - parent = skipSynthesizedParentheses(parent); - node1 = skipSynthesizedParentheses(node1); - node2 = skipSynthesizedParentheses(node2); + parent = skipOuterExpressions(parent, OuterExpressionKinds.SynthesizedParentheses); + node1 = skipOuterExpressions(node1, OuterExpressionKinds.SynthesizedParentheses); + node2 = skipOuterExpressions(node2, OuterExpressionKinds.SynthesizedParentheses); // Always use a newline for synthesized code if the synthesizer desires it. if (node2.startsOnNewLine) { @@ -2518,14 +2533,6 @@ namespace ts { && rangeEndIsOnSameLineAsRangeStart(block, block, currentSourceFile); } - function skipSynthesizedParentheses(node: Node) { - while (node.kind === SyntaxKind.ParenthesizedExpression && nodeIsSynthesized(node)) { - node = (node).expression; - } - - return node; - } - function getTextOfNode(node: Node, includeTrivia?: boolean): string { if (isGeneratedIdentifier(node)) { return generateName(node); @@ -2557,6 +2564,10 @@ namespace ts { return getLiteralText(node, currentSourceFile, languageVersion); } + // + // Name generation + // + /** * Push a new name generation scope. */ @@ -2779,6 +2790,297 @@ namespace ts { // otherwise, return the original node for the source; return node; } + + // + // Comments + // + + function emitNodeConsideringComments(hint: EmitHint, node: Node) { + if (node === undefined) { + return; + } + + const commentRange = getCommentRange(node); + const commentFlags = shouldEmitLeadingOrTrailingComments(node, commentRange); + if ((commentFlags & EmitFlags.NoComments) === EmitFlags.NoComments) { + // always emit any explicit leading or trailing comments. + emitLeadingSynthesizedCommentsOfNode(node); + emitNodeConsideringNestedComments(hint, node, commentFlags, commentRange); + emitTrailingSynthesizedCommentsOfNode(node); + } + else { + // Emit leading comments if the position is not synthesized and the node + // has not opted out from emitting leading comments. + if ((commentFlags & EmitFlags.NoLeadingComments) === 0) { + emitLeadingComments(commentRange.pos, !isNotEmittedStatement(node)); + } + + emitLeadingSynthesizedCommentsOfNode(node); + emitNodeConsideringNestedComments(hint, node, commentFlags, commentRange); + emitTrailingSynthesizedCommentsOfNode(node); + + // Emit trailing comments if the position is not synthesized and the node + // has not opted out from emitting trailing comments. + if ((commentFlags & EmitFlags.NoTrailingComments) === 0 && !isNotEmittedStatement(node)) { + emitTrailingComments(commentRange.end); + } + } + } + + function emitLeadingSynthesizedCommentsOfNode(node: Node) { + if (extendedDiagnostics) performance.mark("emitLeadingSynthesizedComments"); + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + if (extendedDiagnostics) performance.measure("commentTime", "emitLeadingSynthesizedComments"); + } + + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + else { + writer.write(" "); + } + } + + function emitTrailingSynthesizedCommentsOfNode(node: Node) { + if (extendedDiagnostics) performance.mark("emitTrailingSynthesizedComments"); + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if (extendedDiagnostics) performance.measure("commentTime", "emitTrailingSynthesizedComments"); + } + + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.write(" "); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine) { + writer.writeLine(); + } + } + + function writeSynthesizedComment(comment: SynthesizedComment) { + const text = formatSynthesizedComment(comment); + const lineMap = comment.kind === SyntaxKind.MultiLineCommentTrivia ? computeLineStarts(text) : undefined; + writeCommentRange(text, lineMap, writer, 0, text.length, newLine); + } + + function emitNodeConsideringNestedComments(hint: EmitHint, node: Node, commentFlags: EmitFlags, commentRange: TextRange) { + if (commentFlags & EmitFlags.NoNestedComments) { + disabled = true; + getNextPhaseFollowingComments(hint)(hint, node); + disabled = false; + } + else { + emitNodeWithNestedComments(hint, node, commentFlags, commentRange); + } + } + + function emitNodeWithNestedComments(hint: EmitHint, node: Node, commentFlags: EmitFlags, commentRange: TextRange) { + // Save current container state on the stack. + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + + if ((commentFlags & EmitFlags.NoLeadingComments) === 0) { + containerPos = commentRange.pos; + } + + if ((commentFlags & EmitFlags.NoTrailingComments) === 0) { + containerEnd = commentRange.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 = commentRange.end; + } + } + + getNextPhaseFollowingComments(hint)(hint, node); + + // Restore previous container state. + containerPos = savedContainerPos; + containerEnd = savedContainerEnd; + declarationListContainerEnd = savedDeclarationListContainerEnd; + } + + function emitLeadingDetachedComments(detachedRange: TextRange, commentFlags: EmitFlags) { + if (extendedDiagnostics) performance.mark("emitLeadingDetachedCommentsOfBody"); + if (!positionIsSynthesized(detachedRange.pos) && (commentFlags & EmitFlags.NoLeadingComments) === 0) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); + } + if (extendedDiagnostics) performance.measure("commentTime", "emitLeadingDetachedCommentsOfBody"); + } + + function emitTrailingDetachedComments(detachedRange: TextRange, commentFlags: EmitFlags) { + if (disabled) { + return; + } + if (extendedDiagnostics) performance.mark("emitTrailingDetachedComments"); + if (!positionIsSynthesized(detachedRange.end) && (commentFlags & EmitFlags.NoTrailingComments) === 0) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); + } + } + if (extendedDiagnostics) performance.measure("commentTime", "emitTrailingDetachedComments"); + } + + function emitLeadingCommentsOfPosition(pos: number) { + if (disabled || positionIsSynthesized(pos)) { + return; + } + + emitLeadingComments(pos, /*isEmittedNode*/ true); + } + + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + if (extendedDiagnostics) performance.mark("emitLeadingComments"); + hasWrittenComment = false; + if (isEmittedNode) { + forEachLeadingCommentToEmit(pos, emitLeadingComment); + } + else if (pos === 0) { + // 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 + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + } + if (extendedDiagnostics) performance.measure("commentTime", "emitLeadingComments"); + } + + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(currentText, commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } + + function emitLeadingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(currentLineMap, writer, rangePos, commentPos); + hasWrittenComment = true; + } + + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + if (emitPos) emitPos(commentPos); + writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); + if (emitPos) emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.write(" "); + } + } + + function emitTrailingComments(pos: number) { + if (extendedDiagnostics) performance.mark("emitTrailingComments"); + forEachTrailingCommentToEmit(pos, emitTrailingComment); + if (extendedDiagnostics) performance.measure("commentTime", "emitTrailingComments"); + } + + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.write(" "); + } + + if (emitPos) emitPos(commentPos); + writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); + if (emitPos) emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + } + + function emitTrailingCommentsOfPosition(pos: number) { + if (disabled || positionIsSynthesized(pos)) { + return; + } + + if (extendedDiagnostics) performance.mark("emitTrailingCommentsOfPosition"); + forEachTrailingCommentToEmit(pos, emitTrailingCommentOfPosition); + if (extendedDiagnostics) performance.measure("commentTime", "emitTrailingCommentsOfPosition"); + } + + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + + if (emitPos) emitPos(commentPos); + writeCommentRange(currentText, currentLineMap, writer, commentPos, commentEnd, newLine); + if (emitPos) emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.write(" "); + } + } + + function forEachLeadingCommentToEmit(pos: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // 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) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); + } + else { + forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos); + } + } + } + + function forEachTrailingCommentToEmit(end: number, cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean) => void) { + // 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)) { + forEachTrailingCommentRange(currentText, end, cb); + } + } + + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && lastOrUndefined(detachedCommentsInfo).nodePos === pos; + } + + function forEachLeadingCommentWithoutDetachedComments(cb: (commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) => void) { + // get the leading comments from detachedPos + const pos = lastOrUndefined(detachedCommentsInfo).detachedCommentEndPos; + if (detachedCommentsInfo.length - 1) { + detachedCommentsInfo.pop(); + } + else { + detachedCommentsInfo = undefined; + } + + forEachLeadingCommentRange(currentText, pos, cb, /*state*/ pos); + } + + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = emitDetachedComments(currentText, currentLineMap, writer, writeComment, range, newLine, disabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); + } + else { + detachedCommentsInfo = [currentDetachedCommentInfo]; + } + } + } + + function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (emitPos) emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + if (emitPos) emitPos(commentEnd); + } } function createDelimiterMap() { @@ -2811,6 +3113,47 @@ namespace ts { return brackets[format & ListFormat.BracketsMask][1]; } + function shouldEmitLeadingOrTrailingComments(node: Node, commentRange: TextRange) { + let commentFlags = getEmitFlags(node) & (EmitFlags.NoComments | EmitFlags.NoNestedComments); + if (isCollapsedRange(commentRange)) { + commentFlags |= EmitFlags.NoComments; + } + else { + if ((commentFlags & EmitFlags.NoLeadingComments) === 0 && positionIsSynthesized(commentRange.pos)) { + commentFlags |= EmitFlags.NoLeadingComments; + } + if ((commentFlags & EmitFlags.NoTrailingComments) === 0 && positionIsSynthesized(commentRange.end)) { + commentFlags |= EmitFlags.NoTrailingComments; + } + } + return commentFlags; + } + + function formatSynthesizedComment(comment: SynthesizedComment) { + return comment.kind === SyntaxKind.MultiLineCommentTrivia + ? `/*${comment.text}*/` + : `//${comment.text}`; + } + + /** + * Determine if the given comment is a triple-slash + * + * @return true if the comment is a triple-slash comment else false + **/ + function isTripleSlashComment(text: string, commentPos: number, commentEnd: number) { + // 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 (text.charCodeAt(commentPos + 1) === CharacterCodes.slash && + commentPos + 2 < commentEnd && + text.charCodeAt(commentPos + 2) === CharacterCodes.slash) { + const textSubStr = text.substring(commentPos, commentEnd); + return textSubStr.match(fullTripleSlashReferencePathRegEx) || + textSubStr.match(fullTripleSlashAMDReferencePathRegEx) ? + true : false; + } + return false; + } + // Flags enum to track count of temp variables and a few dedicated names const enum TempFlags { Auto = 0x00000000, // No preferred name diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 95821ba10de..51ec900814a 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -3067,7 +3067,10 @@ namespace ts { Assertions = 1 << 1, PartiallyEmittedExpressions = 1 << 2, - All = Parentheses | Assertions | PartiallyEmittedExpressions + All = Parentheses | Assertions | PartiallyEmittedExpressions, + + OnlySynthetic = 1 << 3, + SynthesizedParentheses = Parentheses | OnlySynthetic, } export function skipOuterExpressions(node: Expression, kinds?: OuterExpressionKinds): Expression; @@ -3076,6 +3079,10 @@ namespace ts { let previousNode: Node; do { previousNode = node; + if (kinds & OuterExpressionKinds.OnlySynthetic && !nodeIsSynthesized(node)) { + break; + } + if (kinds & OuterExpressionKinds.Parentheses) { node = skipParentheses(node); } diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 52bb92ee1f0..e8ba6e0b525 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -31,7 +31,6 @@ "transformers/module/system.ts", "transformers/module/es2015.ts", "transformer.ts", - "comments.ts", "sourcemap.ts", "declarationEmitter.ts", "emitter.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 064e5330e9e..5de029f98dc 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -3749,6 +3749,7 @@ } export const enum EmitFlags { + None = 0, SingleLine = 1 << 0, // The contents of this node should be emitted on a single line. AdviseOnEmitNode = 1 << 1, // The printer should invoke the onEmitNode callback when printing this node. NoSubstitution = 1 << 2, // Disables further substitution of an expression. diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index a38578a7a81..d7673ccb3f1 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -3352,7 +3352,7 @@ namespace ts { * @param range A TextRange. */ export function isCollapsedRange(range: TextRange) { - return range.pos === range.end; + return getRangePos(range) === getRangeEnd(range); } /** diff --git a/src/harness/tsconfig.json b/src/harness/tsconfig.json index 32af0eb2601..c701369c2bb 100644 --- a/src/harness/tsconfig.json +++ b/src/harness/tsconfig.json @@ -34,7 +34,6 @@ "../compiler/transformers/module/system.ts", "../compiler/transformers/module/es2015.ts", "../compiler/transformer.ts", - "../compiler/comments.ts", "../compiler/sourcemap.ts", "../compiler/declarationEmitter.ts", "../compiler/emitter.ts", @@ -91,7 +90,7 @@ "rwcRunner.ts", "test262Runner.ts", "runner.ts", - "../server/protocol.d.ts", + "../server/protocol.ts", "../server/session.ts", "../server/client.ts", "../server/editorServices.ts", diff --git a/src/services/tsconfig.json b/src/services/tsconfig.json index 9bebdb6932e..8d51d84e1f2 100644 --- a/src/services/tsconfig.json +++ b/src/services/tsconfig.json @@ -31,7 +31,6 @@ "../compiler/transformers/module/system.ts", "../compiler/transformers/module/es2015.ts", "../compiler/transformer.ts", - "../compiler/comments.ts", "../compiler/sourcemap.ts", "../compiler/declarationEmitter.ts", "../compiler/emitter.ts",