diff --git a/scripts/build/gulp-typescript-oop/index.js b/scripts/build/gulp-typescript-oop/index.js index 3ef5effaf5..ed7a7d64a0 100644 --- a/scripts/build/gulp-typescript-oop/index.js +++ b/scripts/build/gulp-typescript-oop/index.js @@ -1,6 +1,7 @@ // @ts-check const path = require("path"); const child_process = require("child_process"); +const fs = require("fs"); const tsc = require("gulp-typescript"); const Vinyl = require("vinyl"); const { Duplex, Readable } = require("stream"); @@ -42,7 +43,10 @@ function createProject(tsConfigFileName, settings, options) { getVinyl(path) { return inputs.get(path); }, getSourceFile(fileName) { return sourceFiles.get(fileName); }, createSourceFile(fileName, text, languageVersion) { - if (text === undefined) throw new Error("File not cached."); + if (text === undefined) { + text = fs.readFileSync(fileName, "utf8"); + } + /** @type {protocol.SourceFile} */ let file; if (options.parse) { diff --git a/src/compiler/builder.ts b/src/compiler/builder.ts index 2de77fa554..ac03528472 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -450,7 +450,7 @@ namespace ts { assertSourceFileOkWithoutNextAffectedCall(state, targetSourceFile); if (!targetSourceFile) { // Emit and report any errors we ran into. - let sourceMaps: SourceMapData[] = []; + let sourceMaps: SourceMapEmitResult[] = []; let emitSkipped = false; let diagnostics: Diagnostic[] | undefined; let emittedFiles: string[] = []; diff --git a/src/compiler/checker.ts b/src/compiler/checker.ts index 4f11d2ac8d..f06a284a72 100644 --- a/src/compiler/checker.ts +++ b/src/compiler/checker.ts @@ -3141,7 +3141,7 @@ namespace ts { const sig = nodeBuilder.signatureToSignatureDeclaration(signature, sigOutput, enclosingDeclaration, toNodeBuilderFlags(flags) | NodeBuilderFlags.IgnoreErrors | NodeBuilderFlags.WriteTypeParametersInQualifiedName); const printer = createPrinter({ removeComments: true, omitTrailingSemicolon: true }); const sourceFile = enclosingDeclaration && getSourceFileOfNode(enclosingDeclaration); - printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, writer); // TODO: GH#18217 + printer.writeNode(EmitHint.Unspecified, sig!, /*sourceFile*/ sourceFile, getTrailingSemicolonOmittingWriter(writer)); // TODO: GH#18217 return writer; } } diff --git a/src/compiler/comments.ts b/src/compiler/comments.ts deleted file mode 100644 index 485e3e04aa..0000000000 --- a/src/compiler/comments.ts +++ /dev/null @@ -1,431 +0,0 @@ -/* @internal */ -namespace ts { - export interface CommentWriter { - reset(): void; - setSourceFile(sourceFile: SourceFile): void; - setWriter(writer: EmitTextWriter | undefined): void; - emitNodeWithComments(hint: EmitHint, node: Node | undefined, emitCallback: (hint: EmitHint, node: Node) => void): void; - emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void): void; - emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean): 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: ReadonlyArray; - let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined; - 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 || 0; - 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; - // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. - // It is expensive to walk entire tree just to set one kind of node to have no comments. - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; - const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; - - // 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 || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { - // Advance the container position of comments get emitted or if they've been disabled explicitly using NoLeadingComments. - containerPos = pos; - } - - if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { - // As above. - 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 | undefined, 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("beginEmitBodyWithDetachedComments"); - } - - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "beginEmitBodyWithDetachedComments"); - } - } - - 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 shouldWriteComment(text: string, pos: number) { - if (printerOptions.onlyPrintJsDocStyle) { - return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); - } - return true; - } - - function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (!shouldWriteComment(currentText, commentPos)) return; - 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 if (kind === SyntaxKind.MultiLineCommentTrivia) { - 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) { - if (!shouldWriteComment(currentText, commentPos)) return; - // 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, prefixSpace?: boolean) { - if (disabled) { - return; - } - - if (extendedDiagnostics) { - performance.mark("beforeEmitTrailingCommentsOfPosition"); - } - - forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : 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 && last(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 = last(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 (!shouldWriteComment(currentText, commentPos)) return; - 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) { - return isRecognizedTripleSlashComment(currentText, commentPos, commentEnd); - } - } -} diff --git a/src/compiler/core.ts b/src/compiler/core.ts index bbc9098407..3dc2735d00 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1061,10 +1061,10 @@ namespace ts { /** * Stable sort of an array. Elements equal to each other maintain their relative position in the array. */ - export function stableSort(array: ReadonlyArray, comparer: Comparer) { + export function stableSort(array: ReadonlyArray, comparer: Comparer): SortedReadonlyArray { const indices = array.map((_, i) => i); stableSortIndices(array, indices, comparer); - return indices.map(i => array[i]); + return indices.map(i => array[i]) as SortedArray as SortedReadonlyArray; } export function rangeEquals(array1: ReadonlyArray, array2: ReadonlyArray, pos: number, end: number) { @@ -1156,13 +1156,26 @@ namespace ts { * @param offset An offset into `array` at which to start the search. */ export function binarySearch(array: ReadonlyArray, value: T, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { - if (!array || array.length === 0) { + return binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset); + } + + /** + * Performs a binary search, finding the index at which an object with `key` occurs in `array`. + * If no such index is found, returns the 2's-complement of first index at which + * `array[index]` exceeds `key`. + * @param array A sorted array whose first element must be no larger than number + * @param key The key to be searched for in the array. + * @param keySelector A callback used to select the search key from each element of `array`. + * @param keyComparer A callback used to compare two keys in a sorted array. + * @param offset An offset into `array` at which to start the search. + */ + export function binarySearchKey(array: ReadonlyArray, key: U, keySelector: (v: T) => U, keyComparer: Comparer, offset?: number): number { + if (!some(array)) { return -1; } let low = offset || 0; let high = array.length - 1; - const key = keySelector(value); while (low <= high) { const middle = low + ((high - low) >> 1); const midKey = keySelector(array[middle]); diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 2443f72d59..6c36198a41 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -1,6 +1,7 @@ namespace ts { const infoExtension = ".tsbundleinfo"; const brackets = createBracketsMap(); + const syntheticParent: TextRange = { pos: -1, end: -1 }; /*@internal*/ /** @@ -102,28 +103,20 @@ namespace ts { // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature export function emitFiles(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile, emitOnlyDtsFiles?: boolean, transformers?: TransformerFactory[], declarationTransformers?: TransformerFactory[]): EmitResult { const compilerOptions = host.getCompilerOptions(); - const sourceMapDataList: SourceMapData[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; + const sourceMapDataList: SourceMapEmitResult[] | undefined = (compilerOptions.sourceMap || compilerOptions.inlineSourceMap || getAreDeclarationMapsEnabled(compilerOptions)) ? [] : undefined; const emittedFilesList: string[] | undefined = compilerOptions.listEmittedFiles ? [] : undefined; const emitterDiagnostics = createDiagnosticCollection(); - const newLine = host.getNewLine(); + const newLine = getNewLineCharacter(compilerOptions, () => host.getNewLine()); const writer = createTextWriter(newLine); - const sourceMap = createSourceMapWriter(host, writer); - const declarationSourceMap = createSourceMapWriter(host, writer, { - sourceMap: compilerOptions.declarationMap, - sourceRoot: compilerOptions.sourceRoot, - mapRoot: compilerOptions.mapRoot, - extendedDiagnostics: compilerOptions.extendedDiagnostics, - // Explicitly do not passthru either `inline` option - }); - + const { enter, exit } = performance.createTimer("printTime", "beforePrint", "afterPrint"); let bundleInfo: BundleInfo = createDefaultBundleInfo(); let emitSkipped = false; let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; // Emit each output file - performance.mark("beforePrint"); + enter(); forEachEmittedFile(host, emitSourceFileOrBundle, getSourceFilesToEmit(host, targetSourceFile), emitOnlyDtsFiles); - performance.measure("printTime", "beforePrint"); + exit(); return { @@ -172,26 +165,30 @@ namespace ts { // Transform the source files const transform = transformNodes(resolver, host, compilerOptions, [sourceFileOrBundle], transformers!, /*allowDtsFiles*/ false); + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: compilerOptions.noEmitHelpers, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + inlineSources: compilerOptions.inlineSources, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + }; + // Create a printer to print the nodes - const printer = createPrinter({ ...compilerOptions, noEmitHelpers: compilerOptions.noEmitHelpers } as PrinterOptions, { + const printer = createPrinter(printerOptions, { // resolver hooks hasGlobalName: resolver.hasGlobalName, // transform hooks onEmitNode: transform.emitNodeWithNotification, substituteNode: transform.substituteNode, - - // sourcemap hooks - onEmitSourceMapOfNode: sourceMap.emitNodeWithSourceMap, - onEmitSourceMapOfToken: sourceMap.emitTokenWithSourceMap, - onEmitSourceMapOfPosition: sourceMap.emitPos, - - // emitter hooks - onSetSourceFile: setSourceFile, }); Debug.assert(transform.transformed.length === 1, "Should only see one output from the transform"); - printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], bundleInfoPath, printer, sourceMap); + printSourceFileOrBundle(jsFilePath, sourceMapFilePath, transform.transformed[0], bundleInfoPath, printer, compilerOptions); // Clean up emit nodes on parse tree transform.dispose(); @@ -216,16 +213,23 @@ namespace ts { emitterDiagnostics.add(diagnostic); } } - const declarationPrinter = createPrinter({ ...compilerOptions, onlyPrintJsDocStyle: true, noEmitHelpers: true } as PrinterOptions, { + + const printerOptions: PrinterOptions = { + removeComments: compilerOptions.removeComments, + newLine: compilerOptions.newLine, + noEmitHelpers: true, + module: compilerOptions.module, + target: compilerOptions.target, + sourceMap: compilerOptions.sourceMap, + inlineSourceMap: compilerOptions.inlineSourceMap, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + onlyPrintJsDocStyle: true, + }; + + const declarationPrinter = createPrinter(printerOptions, { // resolver hooks hasGlobalName: resolver.hasGlobalName, - // sourcemap hooks - onEmitSourceMapOfNode: declarationSourceMap.emitNodeWithSourceMap, - onEmitSourceMapOfToken: declarationSourceMap.emitTokenWithSourceMap, - onEmitSourceMapOfPosition: declarationSourceMap.emitPos, - onSetSourceFile: setSourceFileForDeclarationSourceMaps, - // transform hooks onEmitNode: declarationTransform.emitNodeWithNotification, substituteNode: declarationTransform.substituteNode, @@ -234,7 +238,13 @@ namespace ts { emitSkipped = emitSkipped || declBlocked; if (!declBlocked || emitOnlyDtsFiles) { Debug.assert(declarationTransform.transformed.length === 1, "Should only see one output from the decl transform"); - printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], /* bundleInfopath*/ undefined, declarationPrinter, declarationSourceMap); + printSourceFileOrBundle(declarationFilePath, declarationMapPath, declarationTransform.transformed[0], /* bundleInfopath*/ undefined, declarationPrinter, { + sourceMap: compilerOptions.declarationMap, + sourceRoot: compilerOptions.sourceRoot, + mapRoot: compilerOptions.mapRoot, + extendedDiagnostics: compilerOptions.extendedDiagnostics, + // Explicitly do not passthru either `inline` option + }); if (emitOnlyDtsFiles && declarationTransform.transformed[0].kind === SyntaxKind.SourceFile) { const sourceFile = declarationTransform.transformed[0] as SourceFile; exportedModulesFromDeclarationEmit = sourceFile.exportedModulesFromDeclarationEmit; @@ -257,29 +267,56 @@ namespace ts { forEachChild(node, collectLinkedAliases); } - function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, bundleInfoPath: string | undefined, printer: Printer, mapRecorder: SourceMapWriter) { + function printSourceFileOrBundle(jsFilePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, bundleInfoPath: string | undefined, printer: Printer, mapOptions: SourceMapOptions) { const bundle = sourceFileOrBundle.kind === SyntaxKind.Bundle ? sourceFileOrBundle : undefined; const sourceFile = sourceFileOrBundle.kind === SyntaxKind.SourceFile ? sourceFileOrBundle : undefined; const sourceFiles = bundle ? bundle.sourceFiles : [sourceFile!]; - mapRecorder.initialize(jsFilePath, sourceMapFilePath || "", sourceFileOrBundle, sourceMapDataList); + + let sourceMapGenerator: SourceMapGenerator | undefined; + if (shouldEmitSourceMaps(mapOptions, sourceFileOrBundle)) { + sourceMapGenerator = createSourceMapGenerator( + host, + getBaseFileName(normalizeSlashes(jsFilePath)), + getSourceRoot(mapOptions), + getSourceMapDirectory(mapOptions, jsFilePath, sourceFile), + mapOptions); + } if (bundle) { - printer.writeBundle(bundle, writer, bundleInfo); + printer.writeBundle(bundle, bundleInfo, writer, sourceMapGenerator); } else { - printer.writeFile(sourceFile!, writer); + printer.writeFile(sourceFile!, writer, sourceMapGenerator); } - writer.writeLine(); + if (sourceMapGenerator) { + if (sourceMapDataList) { + sourceMapDataList.push({ + inputSourceFileNames: sourceMapGenerator.getSources(), + sourceMap: sourceMapGenerator.toJSON() + }); + } - const sourceMappingURL = mapRecorder.getSourceMappingURL(); - if (sourceMappingURL) { - writer.write(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Sometimes tools can sometimes see this line as a source mapping url comment + const sourceMappingURL = getSourceMappingURL( + mapOptions, + sourceMapGenerator, + jsFilePath, + sourceMapFilePath, + sourceFile); + + if (sourceMappingURL) { + if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); + writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment + } + + // Write the source map + if (sourceMapFilePath) { + const sourceMap = sourceMapGenerator.toString(); + writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); + } } - - // Write the source map - if (sourceMapFilePath) { - writeFile(host, emitterDiagnostics, sourceMapFilePath, mapRecorder.getText(), /*writeByteOrderMark*/ false, sourceFiles); + else { + writer.writeLine(); } // Write the output file @@ -292,23 +329,87 @@ namespace ts { } // Reset state - mapRecorder.reset(); writer.clear(); bundleInfo = createDefaultBundleInfo(); } - function setSourceFile(node: SourceFile) { - sourceMap.setSourceFile(node); + interface SourceMapOptions { + sourceMap?: boolean; + inlineSourceMap?: boolean; + inlineSources?: boolean; + sourceRoot?: string; + mapRoot?: string; + extendedDiagnostics?: boolean; } - function setSourceFileForDeclarationSourceMaps(node: SourceFile) { - declarationSourceMap.setSourceFile(node); + function shouldEmitSourceMaps(mapOptions: SourceMapOptions, sourceFileOrBundle: SourceFile | Bundle) { + return (mapOptions.sourceMap || mapOptions.inlineSourceMap) + && (sourceFileOrBundle.kind !== SyntaxKind.SourceFile || !fileExtensionIs(sourceFileOrBundle.fileName, Extension.Json)); + } + + function getSourceRoot(mapOptions: SourceMapOptions) { + // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the + // relative paths of the sources list in the sourcemap + const sourceRoot = normalizeSlashes(mapOptions.sourceRoot || ""); + return sourceRoot ? ensureTrailingDirectorySeparator(sourceRoot) : sourceRoot; + } + + function getSourceMapDirectory(mapOptions: SourceMapOptions, filePath: string, sourceFile: SourceFile | undefined) { + if (mapOptions.sourceRoot) return host.getCommonSourceDirectory(); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); + if (sourceFile) { + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + } + return sourceMapDir; + } + return getDirectoryPath(normalizePath(filePath)); + } + + function getSourceMappingURL(mapOptions: SourceMapOptions, sourceMapGenerator: SourceMapGenerator, filePath: string, sourceMapFilePath: string | undefined, sourceFile: SourceFile | undefined) { + if (mapOptions.inlineSourceMap) { + // Encode the sourceMap into the sourceMap url + const sourceMapText = sourceMapGenerator.toString(); + const base64SourceMapText = base64encode(sys, sourceMapText); + return `data:application/json;base64,${base64SourceMapText}`; + } + + const sourceMapFile = getBaseFileName(normalizeSlashes(Debug.assertDefined(sourceMapFilePath))); + if (mapOptions.mapRoot) { + let sourceMapDir = normalizeSlashes(mapOptions.mapRoot); + if (sourceFile) { + // For modules or multiple emit files the mapRoot will have directory structure like the sources + // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map + sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFile.fileName, host, sourceMapDir)); + } + if (getRootLength(sourceMapDir) === 0) { + // The relative paths are relative to the common directory + sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); + return getRelativePathToDirectoryOrUrl( + getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath + combinePaths(sourceMapDir, sourceMapFile), // this is where user expects to see sourceMap + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + } + else { + return combinePaths(sourceMapDir, sourceMapFile); + } + } + return sourceMapFile; } } const enum PipelinePhase { Notification, + Substitution, Comments, SourceMaps, Emit @@ -317,26 +418,18 @@ namespace ts { export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { const { hasGlobalName, - onEmitSourceMapOfNode, - onEmitSourceMapOfToken, - onEmitSourceMapOfPosition, - onEmitNode, - onSetSourceFile, - substituteNode, + onEmitNode = noEmitNotification, + substituteNode = noEmitSubstitution, onBeforeEmitNodeArray, onAfterEmitNodeArray, onBeforeEmitToken, onAfterEmitToken } = handlers; + const extendedDiagnostics = !!printerOptions.extendedDiagnostics; const newLine = getNewLineCharacter(printerOptions); - const comments = createCommentWriter(printerOptions, onEmitSourceMapOfPosition); - const { - emitNodeWithComments, - emitBodyWithDetachedComments, - emitTrailingCommentsOfPosition, - emitLeadingCommentsOfPosition, - } = comments; + const moduleKind = getEmitModuleKind(printerOptions); + const bundledHelpers = createMap(); let currentSourceFile!: SourceFile; let nodeIdToGeneratedName: string[]; // Map of generated names for specific nodes. @@ -348,20 +441,26 @@ namespace ts { let reservedNames: Map; // TempFlags to reserve in nested name generation scopes. let writer: EmitTextWriter; - let ownWriter: EmitTextWriter; + let ownWriter: EmitTextWriter; // Reusable `EmitTextWriter` for basic printing. let write = writeBase; - let commitPendingSemicolon: typeof commitPendingSemicolonInternal = noop; - let writeSemicolon: typeof writeSemicolonInternal = writeSemicolonInternal; - let pendingSemicolon = false; - if (printerOptions.omitTrailingSemicolon) { - commitPendingSemicolon = commitPendingSemicolonInternal; - writeSemicolon = deferWriteSemicolon; - } - const syntheticParent: TextRange = { pos: -1, end: -1 }; - const moduleKind = getEmitModuleKind(printerOptions); - const bundledHelpers = createMap(); let isOwnFileEmit: boolean; + // Source Maps + let sourceMapsDisabled = true; + let sourceMapGenerator: SourceMapGenerator | undefined; + let sourceMapSource: SourceMapSource; + let sourceMapSourceIndex = -1; + + // Comments + let containerPos = -1; + let containerEnd = -1; + let declarationListContainerEnd = -1; + let currentLineMap: ReadonlyArray | undefined; + let detachedCommentsInfo: { nodePos: number, detachedCommentEndPos: number}[] | undefined; + let hasWrittenComment = false; + let commentsDisabled = !!printerOptions.removeComments; + const { enter: enterComment, exit: exitComment } = performance.createTimerIf(extendedDiagnostics, "commentTime", "beforeComment", "afterComment"); + reset(); return { // public API @@ -404,12 +503,12 @@ namespace ts { } function printBundle(bundle: Bundle): string { - writeBundle(bundle, beginPrint()); + writeBundle(bundle, /*bundleInfo*/ undefined, beginPrint(), /*sourceMapEmitter*/ undefined); return endPrint(); } function printFile(sourceFile: SourceFile): string { - writeFile(sourceFile, beginPrint()); + writeFile(sourceFile, beginPrint(), /*sourceMapEmitter*/ undefined); return endPrint(); } @@ -425,7 +524,7 @@ namespace ts { function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile, output: EmitTextWriter): void; function writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, output: EmitTextWriter) { const previousWriter = writer; - setWriter(output); + setWriter(output, /*_sourceMapGenerator*/ undefined); print(hint, node, sourceFile); reset(); writer = previousWriter; @@ -433,7 +532,7 @@ namespace ts { function writeList(format: ListFormat, nodes: NodeArray, sourceFile: SourceFile | undefined, output: EmitTextWriter) { const previousWriter = writer; - setWriter(output); + setWriter(output, /*_sourceMapGenerator*/ undefined); if (sourceFile) { setSourceFile(sourceFile); } @@ -442,18 +541,18 @@ namespace ts { writer = previousWriter; } - function writeBundle(bundle: Bundle, output: EmitTextWriter, bundleInfo?: BundleInfo) { + function writeBundle(bundle: Bundle, bundleInfo: BundleInfo | undefined, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { isOwnFileEmit = false; const previousWriter = writer; - setWriter(output); + setWriter(output, sourceMapGenerator); emitShebangIfNeeded(bundle); emitPrologueDirectivesIfNeeded(bundle); emitHelpers(bundle); emitSyntheticTripleSlashReferencesIfNeeded(bundle); for (const prepend of bundle.prepends) { - print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); writeLine(); + print(EmitHint.Unspecified, prepend, /*sourceFile*/ undefined); } if (bundleInfo) { @@ -469,16 +568,16 @@ namespace ts { function writeUnparsedSource(unparsed: UnparsedSource, output: EmitTextWriter) { const previousWriter = writer; - setWriter(output); + setWriter(output, /*_sourceMapGenerator*/ undefined); print(EmitHint.Unspecified, unparsed, /*sourceFile*/ undefined); reset(); writer = previousWriter; } - function writeFile(sourceFile: SourceFile, output: EmitTextWriter) { + function writeFile(sourceFile: SourceFile, output: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined) { isOwnFileEmit = true; const previousWriter = writer; - setWriter(output); + setWriter(output, sourceMapGenerator); emitShebangIfNeeded(sourceFile); emitPrologueDirectivesIfNeeded(sourceFile); print(EmitHint.SourceFile, sourceFile, sourceFile); @@ -501,21 +600,25 @@ namespace ts { setSourceFile(sourceFile); } - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, hint); + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); pipelinePhase(hint, node); } function setSourceFile(sourceFile: SourceFile) { currentSourceFile = sourceFile; - comments.setSourceFile(sourceFile); - if (onSetSourceFile) { - onSetSourceFile(sourceFile); - } + currentLineMap = undefined; + detachedCommentsInfo = undefined; + setSourceMapSource(sourceFile); } - function setWriter(output: EmitTextWriter | undefined) { - writer = output!; // TODO: GH#18217 - comments.setWriter(output!); + function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { + if (_writer && printerOptions.omitTrailingSemicolon) { + _writer = getTrailingSemicolonOmittingWriter(_writer); + } + + writer = _writer!; // TODO: GH#18217 + sourceMapGenerator = _sourceMapGenerator; + sourceMapsDisabled = !writer || !sourceMapGenerator; } function reset() { @@ -525,44 +628,56 @@ namespace ts { tempFlagsStack = []; tempFlags = TempFlags.Auto; reservedNamesStack = []; - comments.reset(); - setWriter(/*output*/ undefined); + currentSourceFile = undefined!; + currentLineMap = undefined!; + detachedCommentsInfo = undefined; + setWriter(/*output*/ undefined, /*_sourceMapGenerator*/ undefined); + } + + function getCurrentLineMap() { + return currentLineMap || (currentLineMap = getLineStarts(currentSourceFile)); } function emit(node: Node | undefined) { - if (!node) return; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Unspecified); + if (node === undefined) return; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); pipelinePhase(EmitHint.Unspecified, node); } function emitIdentifierName(node: Identifier | undefined) { - if (!node) return; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.IdentifierName); + if (node === undefined) return; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); pipelinePhase(EmitHint.IdentifierName, node); } function emitExpression(node: Expression | undefined) { - if (!node) return; - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.Expression); + if (node === undefined) return; + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); pipelinePhase(EmitHint.Expression, node); } - function getPipelinePhase(phase: PipelinePhase, hint: EmitHint) { + function getPipelinePhase(phase: PipelinePhase, node: Node) { switch (phase) { case PipelinePhase.Notification: - if (onEmitNode) { + if (onEmitNode !== noEmitNotification) { return pipelineEmitWithNotification; } // falls through + case PipelinePhase.Substitution: + if (substituteNode !== noEmitSubstitution) { + return pipelineEmitWithSubstitution; + } + // falls through + case PipelinePhase.Comments: - if (emitNodeWithComments && hint !== EmitHint.SourceFile) { + if (!commentsDisabled && node.kind !== SyntaxKind.SourceFile) { return pipelineEmitWithComments; } - return pipelineEmitWithoutComments; + // falls through case PipelinePhase.SourceMaps: - if (onEmitSourceMapOfNode && hint !== EmitHint.SourceFile) { + if (!sourceMapsDisabled && node.kind !== SyntaxKind.SourceFile && !isInJsonFile(node)) { return pipelineEmitWithSourceMap; } // falls through @@ -571,38 +686,27 @@ namespace ts { return pipelineEmitWithHint; default: - return Debug.assertNever(phase, `Unexpected value for PipelinePhase: ${phase}`); + return Debug.assertNever(phase); } } - function getNextPipelinePhase(currentPhase: PipelinePhase, hint: EmitHint) { - return getPipelinePhase(currentPhase + 1, hint); + function getNextPipelinePhase(currentPhase: PipelinePhase, node: Node) { + return getPipelinePhase(currentPhase + 1, node); } function pipelineEmitWithNotification(hint: EmitHint, node: Node) { - Debug.assertDefined(onEmitNode)(hint, node, getNextPipelinePhase(PipelinePhase.Notification, hint)); - } - - function pipelineEmitWithComments(hint: EmitHint, node: Node) { - Debug.assertDefined(emitNodeWithComments); - Debug.assert(hint !== EmitHint.SourceFile); - emitNodeWithComments(hint, trySubstituteNode(hint, node), getNextPipelinePhase(PipelinePhase.Comments, hint)); - } - - function pipelineEmitWithoutComments(hint: EmitHint, node: Node) { - const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, hint); - pipelinePhase(hint, trySubstituteNode(hint, node)); - } - - function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { - Debug.assert(hint !== EmitHint.SourceFile); - Debug.assertDefined(onEmitSourceMapOfNode)(hint, node, pipelineEmitWithHint); + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Notification, node); + onEmitNode(hint, node, pipelinePhase); } function pipelineEmitWithHint(hint: EmitHint, node: Node): void { if (hint === EmitHint.SourceFile) return emitSourceFile(cast(node, isSourceFile)); if (hint === EmitHint.IdentifierName) return emitIdentifier(cast(node, isIdentifier)); if (hint === EmitHint.MappedTypeParameter) return emitMappedTypeParameter(cast(node, isTypeParameterDeclaration)); + if (hint === EmitHint.EmbeddedStatement) { + Debug.assertNode(node, isEmptyStatement); + return emitEmptyStatement(/*isEmbeddedStatement*/ true); + } if (hint === EmitHint.Unspecified) { if (isKeyword(node.kind)) return writeTokenNode(node, writeKeyword); @@ -703,10 +807,10 @@ namespace ts { case SyntaxKind.ImportType: return emitImportTypeNode(node); case SyntaxKind.JSDocAllType: - write("*"); + writePunctuation("*"); return; case SyntaxKind.JSDocUnknownType: - write("?"); + writePunctuation("?"); return; case SyntaxKind.JSDocNullableType: return emitJSDocNullableType(node as JSDocNullableType); @@ -738,7 +842,7 @@ namespace ts { case SyntaxKind.VariableStatement: return emitVariableStatement(node); case SyntaxKind.EmptyStatement: - return emitEmptyStatement(); + return emitEmptyStatement(/*isEmbeddedStatement*/ false); case SyntaxKind.ExpressionStatement: return emitExpressionStatement(node); case SyntaxKind.IfStatement: @@ -895,7 +999,9 @@ namespace ts { if (isExpression(node)) { hint = EmitHint.Expression; - node = trySubstituteNode(EmitHint.Expression, node); + if (substituteNode !== noEmitSubstitution) { + node = substituteNode(hint, node); + } } else if (isToken(node)) { return writeTokenNode(node, writePunctuation); @@ -1009,8 +1115,9 @@ namespace ts { emit(node.constraint); } - function trySubstituteNode(hint: EmitHint, node: Node) { - return node && substituteNode && substituteNode(hint, node) || node; + function pipelineEmitWithSubstitution(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Substitution, node); + pipelinePhase(hint, substituteNode(hint, node)); } function emitHelpers(node: Node) { @@ -1183,7 +1290,7 @@ namespace ts { emitNodeWithWriter(node.name, writeProperty); emit(node.questionToken); emitTypeAnnotation(node.type); - writeSemicolon(); + writeTrailingSemicolon(); } function emitPropertyDeclaration(node: PropertyDeclaration) { @@ -1194,7 +1301,7 @@ namespace ts { emit(node.exclamationToken); emitTypeAnnotation(node.type); emitInitializer(node.initializer, node.type ? node.type.end : node.questionToken ? node.questionToken.end : node.name.end, node); - writeSemicolon(); + writeTrailingSemicolon(); } function emitMethodSignature(node: MethodSignature) { @@ -1206,7 +1313,7 @@ namespace ts { emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); emitTypeAnnotation(node.type); - writeSemicolon(); + writeTrailingSemicolon(); popNameGenerationScope(node); } @@ -1241,7 +1348,7 @@ namespace ts { emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); emitTypeAnnotation(node.type); - writeSemicolon(); + writeTrailingSemicolon(); popNameGenerationScope(node); } @@ -1254,7 +1361,7 @@ namespace ts { emitTypeParameters(node, node.typeParameters); emitParameters(node, node.parameters); emitTypeAnnotation(node.type); - writeSemicolon(); + writeTrailingSemicolon(); popNameGenerationScope(node); } @@ -1263,11 +1370,11 @@ namespace ts { emitModifiers(node, node.modifiers); emitParametersForIndexSignature(node, node.parameters); emitTypeAnnotation(node.type); - writeSemicolon(); + writeTrailingSemicolon(); } function emitSemicolonClassElement() { - writeSemicolon(); + writeTrailingSemicolon(); } // @@ -1299,26 +1406,26 @@ namespace ts { } function emitJSDocFunctionType(node: JSDocFunctionType) { - write("function"); + writeKeyword("function"); emitParameters(node, node.parameters); - write(":"); + writePunctuation(":"); emit(node.type); } function emitJSDocNullableType(node: JSDocNullableType) { - write("?"); + writePunctuation("?"); emit(node.type); } function emitJSDocNonNullableType(node: JSDocNonNullableType) { - write("!"); + writePunctuation("!"); emit(node.type); } function emitJSDocOptionalType(node: JSDocOptionalType) { emit(node.type); - write("="); + writePunctuation("="); } function emitConstructorType(node: ConstructorTypeNode) { @@ -1354,7 +1461,7 @@ namespace ts { } function emitRestOrJSDocVariadicType(node: RestTypeNode | JSDocVariadicType) { - write("..."); + writePunctuation("..."); emit(node.type); } @@ -1366,7 +1473,7 @@ namespace ts { function emitOptionalType(node: OptionalTypeNode) { emit(node.type); - write("?"); + writePunctuation("?"); } function emitUnionType(node: UnionTypeNode) { @@ -1441,7 +1548,7 @@ namespace ts { } writePunctuation("["); - const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, EmitHint.MappedTypeParameter); + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node.typeParameter); pipelinePhase(EmitHint.MappedTypeParameter, node.typeParameter); writePunctuation("]"); @@ -1454,7 +1561,7 @@ namespace ts { writePunctuation(":"); writeSpace(); emit(node.type); - writeSemicolon(); + writeTrailingSemicolon(); if (emitFlags & EmitFlags.SingleLine) { writeSpace(); } @@ -1553,7 +1660,7 @@ namespace ts { } emitExpression(node.expression); - increaseIndentIf(indentBeforeDot); + increaseIndentIf(indentBeforeDot, /*writeSpaceIfNotIndenting*/ false); const shouldEmitDotDot = !indentBeforeDot && needsDotDotForPropertyAccess(node.expression); if (shouldEmitDotDot) { @@ -1561,7 +1668,7 @@ namespace ts { } emitTokenWithComment(SyntaxKind.DotToken, node.expression.end, writePunctuation, node); - increaseIndentIf(indentAfterDot); + increaseIndentIf(indentAfterDot, /*writeSpaceIfNotIndenting*/ false); emit(node.name); decreaseIndentIf(indentBeforeDot, indentAfterDot); } @@ -1708,11 +1815,11 @@ namespace ts { const indentAfterOperator = needsIndentation(node, node.operatorToken, node.right); emitExpression(node.left); - increaseIndentIf(indentBeforeOperator, isCommaOperator ? " " : undefined); + increaseIndentIf(indentBeforeOperator, isCommaOperator); emitLeadingCommentsOfPosition(node.operatorToken.pos); - writeTokenNode(node.operatorToken, writeOperator); + writeTokenNode(node.operatorToken, node.operatorToken.kind === SyntaxKind.InKeyword ? writeKeyword : writeOperator); emitTrailingCommentsOfPosition(node.operatorToken.end, /*prefixSpace*/ true); // Binary operators should have a space before the comment starts - increaseIndentIf(indentAfterOperator, " "); + increaseIndentIf(indentAfterOperator, /*writeSpaceIfNotIndenting*/ true); emitExpression(node.right); decreaseIndentIf(indentBeforeOperator, indentAfterOperator); } @@ -1724,15 +1831,15 @@ namespace ts { const indentAfterColon = needsIndentation(node, node.colonToken, node.whenFalse); emitExpression(node.condition); - increaseIndentIf(indentBeforeQuestion, " "); + increaseIndentIf(indentBeforeQuestion, /*writeSpaceIfNotIndenting*/ true); emit(node.questionToken); - increaseIndentIf(indentAfterQuestion, " "); + increaseIndentIf(indentAfterQuestion, /*writeSpaceIfNotIndenting*/ true); emitExpression(node.whenTrue); decreaseIndentIf(indentBeforeQuestion, indentAfterQuestion); - increaseIndentIf(indentBeforeColon, " "); + increaseIndentIf(indentBeforeColon, /*writeSpaceIfNotIndenting*/ true); emit(node.colonToken); - increaseIndentIf(indentAfterColon, " "); + increaseIndentIf(indentAfterColon, /*writeSpaceIfNotIndenting*/ true); emitExpression(node.whenFalse); decreaseIndentIf(indentBeforeColon, indentAfterColon); } @@ -1811,19 +1918,27 @@ namespace ts { function emitVariableStatement(node: VariableStatement) { emitModifiers(node, node.modifiers); emit(node.declarationList); - writeSemicolon(); + writeTrailingSemicolon(); } - function emitEmptyStatement() { - writeSemicolon(); + function emitEmptyStatement(isEmbeddedStatement: boolean) { + // While most trailing semicolons are possibly insignificant, an embedded "empty" + // statement is significant and cannot be elided by a trailing-semicolon-omitting writer. + if (isEmbeddedStatement) { + writePunctuation(";"); + } + else { + writeTrailingSemicolon(); + } } + function emitExpressionStatement(node: ExpressionStatement) { emitExpression(node.expression); // Emit semicolon in non json files // or if json file that created synthesized expression(eg.define expression statement when --out and amd code generation) if (!isJsonSourceFile(currentSourceFile) || nodeIsSynthesized(node.expression)) { - writeSemicolon(); + writeTrailingSemicolon(); } } @@ -1879,9 +1994,9 @@ namespace ts { writeSpace(); let pos = emitTokenWithComment(SyntaxKind.OpenParenToken, openParenPos, writePunctuation, /*contextNode*/ node); emitForBinding(node.initializer); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writeSemicolon, node); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.initializer ? node.initializer.end : pos, writePunctuation, node); emitExpressionWithLeadingSpace(node.condition); - pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writeSemicolon, node); + pos = emitTokenWithComment(SyntaxKind.SemicolonToken, node.condition ? node.condition.end : pos, writePunctuation, node); emitExpressionWithLeadingSpace(node.incrementor); emitTokenWithComment(SyntaxKind.CloseParenToken, node.incrementor ? node.incrementor.end : pos, writePunctuation, node); emitEmbeddedStatement(node, node.statement); @@ -1928,13 +2043,13 @@ namespace ts { function emitContinueStatement(node: ContinueStatement) { emitTokenWithComment(SyntaxKind.ContinueKeyword, node.pos, writeKeyword, node); emitWithLeadingSpace(node.label); - writeSemicolon(); + writeTrailingSemicolon(); } function emitBreakStatement(node: BreakStatement) { emitTokenWithComment(SyntaxKind.BreakKeyword, node.pos, writeKeyword, node); emitWithLeadingSpace(node.label); - writeSemicolon(); + writeTrailingSemicolon(); } function emitTokenWithComment(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode: Node, indentLeading?: boolean) { @@ -1964,7 +2079,7 @@ namespace ts { function emitReturnStatement(node: ReturnStatement) { emitTokenWithComment(SyntaxKind.ReturnKeyword, node.pos, writeKeyword, /*contextNode*/ node); emitExpressionWithLeadingSpace(node.expression); - writeSemicolon(); + writeTrailingSemicolon(); } function emitWithStatement(node: WithStatement) { @@ -1996,7 +2111,7 @@ namespace ts { function emitThrowStatement(node: ThrowStatement) { emitTokenWithComment(SyntaxKind.ThrowKeyword, node.pos, writeKeyword, node); emitExpressionWithLeadingSpace(node.expression); - writeSemicolon(); + writeTrailingSemicolon(); } function emitTryStatement(node: TryStatement) { @@ -2017,7 +2132,7 @@ namespace ts { function emitDebuggerStatement(node: DebuggerStatement) { writeToken(SyntaxKind.DebuggerKeyword, node.pos, writeKeyword); - writeSemicolon(); + writeTrailingSemicolon(); } // @@ -2088,7 +2203,7 @@ namespace ts { } else { emitSignatureHead(node); - writeSemicolon(); + writeTrailingSemicolon(); } } @@ -2234,7 +2349,7 @@ namespace ts { writePunctuation("="); writeSpace(); emit(node.type); - writeSemicolon(); + writeTrailingSemicolon(); } function emitEnumDeclaration(node: EnumDeclaration) { @@ -2258,7 +2373,7 @@ namespace ts { emit(node.name); let body = node.body; - if (!body) return writeSemicolon(); + if (!body) return writeTrailingSemicolon(); while (body.kind === SyntaxKind.ModuleDeclaration) { writePunctuation("."); emit((body).name); @@ -2291,7 +2406,7 @@ namespace ts { emitTokenWithComment(SyntaxKind.EqualsToken, node.name.end, writePunctuation, node); writeSpace(); emitModuleReference(node.moduleReference); - writeSemicolon(); + writeTrailingSemicolon(); } function emitModuleReference(node: ModuleReference) { @@ -2314,7 +2429,7 @@ namespace ts { writeSpace(); } emitExpression(node.moduleSpecifier); - writeSemicolon(); + writeTrailingSemicolon(); } function emitImportClause(node: ImportClause) { @@ -2353,7 +2468,7 @@ namespace ts { } writeSpace(); emitExpression(node.expression); - writeSemicolon(); + writeTrailingSemicolon(); } function emitExportDeclaration(node: ExportDeclaration) { @@ -2372,7 +2487,7 @@ namespace ts { writeSpace(); emitExpression(node.moduleSpecifier); } - writeSemicolon(); + writeTrailingSemicolon(); } function emitNamespaceExportDeclaration(node: NamespaceExportDeclaration) { @@ -2383,7 +2498,7 @@ namespace ts { nextPos = emitTokenWithComment(SyntaxKind.NamespaceKeyword, nextPos, writeKeyword, node); writeSpace(); emit(node.name); - writeSemicolon(); + writeTrailingSemicolon(); } function emitNamedExports(node: NamedExports) { @@ -2461,7 +2576,6 @@ namespace ts { } function emitJsxText(node: JsxText) { - commitPendingSemicolon(); writer.writeLiteral(getTextOfNode(node, /*includeTrivia*/ true)); } @@ -2792,34 +2906,34 @@ namespace ts { function emitTripleSlashDirectives(hasNoDefaultLib: boolean, files: ReadonlyArray, types: ReadonlyArray, libs: ReadonlyArray) { if (hasNoDefaultLib) { - write(`/// `); + writeComment(`/// `); writeLine(); } if (currentSourceFile && currentSourceFile.moduleName) { - write(`/// `); + writeComment(`/// `); writeLine(); } if (currentSourceFile && currentSourceFile.amdDependencies) { for (const dep of currentSourceFile.amdDependencies) { if (dep.name) { - write(`/// `); + writeComment(`/// `); } else { - write(`/// `); + writeComment(`/// `); } writeLine(); } } for (const directive of files) { - write(`/// `); + writeComment(`/// `); writeLine(); } for (const directive of types) { - write(`/// `); + writeComment(`/// `); writeLine(); } for (const directive of libs) { - write(`/// `); + writeComment(`/// `); writeLine(); } } @@ -2891,7 +3005,7 @@ namespace ts { if (isSourceFile(sourceFileOrBundle)) { const shebang = getShebang(sourceFileOrBundle.text); if (shebang) { - write(shebang); + writeComment(shebang); writeLine(); return true; } @@ -2978,7 +3092,13 @@ namespace ts { else { writeLine(); increaseIndent(); - emit(node); + if (isEmptyStatement(node)) { + const pipelinePhase = getPipelinePhase(PipelinePhase.Notification, node); + pipelinePhase(EmitHint.EmbeddedStatement, node); + } + else { + emit(node); + } decreaseIndent(); } } @@ -3225,89 +3345,71 @@ namespace ts { } } - function commitPendingSemicolonInternal() { - if (pendingSemicolon) { - writeSemicolonInternal(); - pendingSemicolon = false; - } - } + // Writers function writeLiteral(s: string) { - commitPendingSemicolon(); writer.writeLiteral(s); } function writeStringLiteral(s: string) { - commitPendingSemicolon(); writer.writeStringLiteral(s); } function writeBase(s: string) { - commitPendingSemicolon(); writer.write(s); } function writeSymbol(s: string, sym: Symbol) { - commitPendingSemicolon(); writer.writeSymbol(s, sym); } function writePunctuation(s: string) { - commitPendingSemicolon(); writer.writePunctuation(s); } - function deferWriteSemicolon() { - pendingSemicolon = true; - } - - function writeSemicolonInternal() { - writer.writePunctuation(";"); + function writeTrailingSemicolon() { + writer.writeTrailingSemicolon(";"); } function writeKeyword(s: string) { - commitPendingSemicolon(); writer.writeKeyword(s); } function writeOperator(s: string) { - commitPendingSemicolon(); writer.writeOperator(s); } function writeParameter(s: string) { - commitPendingSemicolon(); writer.writeParameter(s); } + function writeComment(s: string) { + writer.writeComment(s); + } + function writeSpace() { - commitPendingSemicolon(); writer.writeSpace(" "); } function writeProperty(s: string) { - commitPendingSemicolon(); writer.writeProperty(s); } function writeLine() { - commitPendingSemicolon(); writer.writeLine(); } function increaseIndent() { - commitPendingSemicolon(); writer.increaseIndent(); } function decreaseIndent() { - commitPendingSemicolon(); writer.decreaseIndent(); } function writeToken(token: SyntaxKind, pos: number, writer: (s: string) => void, contextNode?: Node) { - return onEmitSourceMapOfToken - ? onEmitSourceMapOfToken(contextNode, token, writer, pos, writeTokenText) + return !sourceMapsDisabled + ? emitTokenWithSourceMap(contextNode, token, writer, pos, writeTokenText) : writeTokenText(token, writer, pos); } @@ -3346,18 +3448,18 @@ namespace ts { if (line.length) { writeLine(); write(line); - writeLine(); + writer.rawWrite(newLine); } } } - function increaseIndentIf(value: boolean, valueToWriteWhenNotIndenting?: string) { + function increaseIndentIf(value: boolean, writeSpaceIfNotIndenting: boolean) { if (value) { increaseIndent(); writeLine(); } - else if (valueToWriteWhenNotIndenting) { - write(valueToWriteWhenNotIndenting); + else if (writeSpaceIfNotIndenting) { + writeSpace(); } } @@ -3365,7 +3467,7 @@ namespace ts { // previous indent values to be considered at a time. This also allows caller to just // call this once, passing in all their appropriate indent values, instead of needing // to call this helper function multiple times. - function decreaseIndentIf(value1: boolean, value2?: boolean) { + function decreaseIndentIf(value1: boolean, value2: boolean) { if (value1) { decreaseIndent(); } @@ -3908,6 +4010,448 @@ namespace ts { // otherwise, return the original node for the source; return node; } + + // Comments + + function pipelineEmitWithComments(hint: EmitHint, node: Node) { + enterComment(); + hasWrittenComment = false; + const emitFlags = getEmitFlags(node); + const { pos, end } = getCommentRange(node); + const isEmittedNode = node.kind !== SyntaxKind.NotEmittedStatement; + + // We have to explicitly check that the node is JsxText because if the compilerOptions.jsx is "preserve" we will not do any transformation. + // It is expensive to walk entire tree just to set one kind of node to have no comments. + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0 || node.kind === SyntaxKind.JsxText; + const skipTrailingComments = end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0 || node.kind === SyntaxKind.JsxText; + + // Save current container state on the stack. + const savedContainerPos = containerPos; + const savedContainerEnd = containerEnd; + const savedDeclarationListContainerEnd = declarationListContainerEnd; + if ((pos > 0 || end > 0) && pos !== end) { + // 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); + } + + if (!skipLeadingComments || (pos >= 0 && (emitFlags & EmitFlags.NoLeadingComments) !== 0)) { + // Advance the container position if comments get emitted or if they've been disabled explicitly using NoLeadingComments. + containerPos = pos; + } + + if (!skipTrailingComments || (end >= 0 && (emitFlags & EmitFlags.NoTrailingComments) !== 0)) { + // As above. + 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; + } + } + } + forEach(getSyntheticLeadingComments(node), emitLeadingSynthesizedComment); + exitComment(); + + const pipelinePhase = getNextPipelinePhase(PipelinePhase.Comments, node); + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = true; + pipelinePhase(hint, node); + commentsDisabled = false; + } + else { + pipelinePhase(hint, node); + } + + enterComment(); + forEach(getSyntheticTrailingComments(node), emitTrailingSynthesizedComment); + if ((pos > 0 || end > 0) && pos !== end) { + // 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); + } + } + exitComment(); + } + + function emitLeadingSynthesizedComment(comment: SynthesizedComment) { + if (comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + writeSynthesizedComment(comment); + if (comment.hasTrailingNewLine || comment.kind === SyntaxKind.SingleLineCommentTrivia) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); + } + } + + function emitTrailingSynthesizedComment(comment: SynthesizedComment) { + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + 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 emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { + enterComment(); + const { pos, end } = detachedRange; + const emitFlags = getEmitFlags(node); + const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; + const skipTrailingComments = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); + } + + exitComment(); + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); + } + + enterComment(); + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); + } + } + exitComment(); + + } + + 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 shouldWriteComment(text: string, pos: number) { + if (printerOptions.onlyPrintJsDocStyle) { + return (isJSDocLikeText(text, pos) || isPinnedComment(text, pos)); + } + return true; + } + + function emitLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (!shouldWriteComment(currentSourceFile.text, commentPos)) return; + if (!hasWrittenComment) { + emitNewLineBeforeLeadingCommentOfPosition(getCurrentLineMap(), writer, rangePos, commentPos); + hasWrittenComment = true; + } + + // Leading comments are emitted at /*leading comment1 */space/*leading comment*/space + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + else if (kind === SyntaxKind.MultiLineCommentTrivia) { + writer.writeSpace(" "); + } + } + + function emitLeadingCommentsOfPosition(pos: number) { + if (commentsDisabled || pos === -1) { + return; + } + + emitLeadingComments(pos, /*isEmittedNode*/ true); + } + + function emitTrailingComments(pos: number) { + forEachTrailingCommentToEmit(pos, emitTrailingComment); + } + + function emitTrailingComment(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + if (!shouldWriteComment(currentSourceFile.text, commentPos)) return; + // trailing comments are emitted at space/*trailing comment1 */space/*trailing comment2*/ + if (!writer.isAtStartOfLine()) { + writer.writeSpace(" "); + } + + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + } + + function emitTrailingCommentsOfPosition(pos: number, prefixSpace?: boolean) { + if (commentsDisabled) { + return; + } + enterComment(); + forEachTrailingCommentToEmit(pos, prefixSpace ? emitTrailingComment : emitTrailingCommentOfPosition); + exitComment(); + } + + function emitTrailingCommentOfPosition(commentPos: number, commentEnd: number, _kind: SyntaxKind, hasTrailingNewLine: boolean) { + // trailing comments of a position are emitted at /*trailing comment1 */space/*trailing comment*/space + + emitPos(commentPos); + writeCommentRange(currentSourceFile.text, getCurrentLineMap(), writer, commentPos, commentEnd, newLine); + emitPos(commentEnd); + + if (hasTrailingNewLine) { + writer.writeLine(); + } + else { + writer.writeSpace(" "); + } + } + + 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 (currentSourceFile && (containerPos === -1 || pos !== containerPos)) { + if (hasDetachedComments(pos)) { + forEachLeadingCommentWithoutDetachedComments(cb); + } + else { + forEachLeadingCommentRange(currentSourceFile.text, 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 (currentSourceFile && (containerEnd === -1 || (end !== containerEnd && end !== declarationListContainerEnd))) { + forEachTrailingCommentRange(currentSourceFile.text, end, cb); + } + } + + function hasDetachedComments(pos: number) { + return detachedCommentsInfo !== undefined && last(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 = last(detachedCommentsInfo!).detachedCommentEndPos; + if (detachedCommentsInfo!.length - 1) { + detachedCommentsInfo!.pop(); + } + else { + detachedCommentsInfo = undefined; + } + + forEachLeadingCommentRange(currentSourceFile.text, pos, cb, /*state*/ pos); + } + + function emitDetachedCommentsAndUpdateCommentsInfo(range: TextRange) { + const currentDetachedCommentInfo = emitDetachedComments(currentSourceFile.text, getCurrentLineMap(), writer, emitComment, range, newLine, commentsDisabled); + if (currentDetachedCommentInfo) { + if (detachedCommentsInfo) { + detachedCommentsInfo.push(currentDetachedCommentInfo); + } + else { + detachedCommentsInfo = [currentDetachedCommentInfo]; + } + } + } + + function emitComment(text: string, lineMap: number[], writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) { + if (!shouldWriteComment(currentSourceFile.text, commentPos)) return; + emitPos(commentPos); + writeCommentRange(text, lineMap, writer, commentPos, commentEnd, newLine); + 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) { + return isRecognizedTripleSlashComment(currentSourceFile.text, commentPos, commentEnd); + } + + // Source Maps + + function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { + const pipelinePhase = getNextPipelinePhase(PipelinePhase.SourceMaps, node); + if (isUnparsedSource(node) && node.sourceMapText !== undefined) { + const parsed = tryParseRawSourceMap(node.sourceMapText); + if (parsed) { + sourceMapGenerator!.appendSourceMap( + writer.getLine(), + writer.getColumn(), + parsed, + node.sourceMapPath!); + } + pipelinePhase(hint, node); + } + else { + const { pos, end, source = sourceMapSource } = getSourceMapRange(node); + const emitFlags = getEmitFlags(node); + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && pos >= 0) { + emitSourcePos(source, skipSourceTrivia(source, pos)); + } + + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = true; + pipelinePhase(hint, node); + sourceMapsDisabled = false; + } + else { + pipelinePhase(hint, node); + } + + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && end >= 0) { + emitSourcePos(source, end); + } + } + } + + /** + * Skips trivia such as comments and white-space that can optionally overriden by the source map source + */ + function skipSourceTrivia(source: SourceMapSource, pos: number): number { + return source.skipTrivia ? source.skipTrivia(pos) : skipTrivia(sourceMapSource.text, pos); + } + + /** + * Emits a mapping. + * + * If the position is synthetic (undefined or a negative value), no mapping will be + * created. + * + * @param pos The position. + */ + function emitPos(pos: number) { + if (sourceMapsDisabled || positionIsSynthesized(pos) || isJsonSourceMapSource(sourceMapSource)) { + return; + } + + const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(currentSourceFile, pos); + sourceMapGenerator!.addMapping( + writer.getLine(), + writer.getColumn(), + sourceMapSourceIndex, + sourceLine, + sourceCharacter, + /*nameIndex*/ undefined); + } + + function emitSourcePos(source: SourceMapSource, pos: number) { + if (source !== sourceMapSource) { + const savedSourceMapSource = sourceMapSource; + setSourceMapSource(source); + emitPos(pos); + setSourceMapSource(savedSourceMapSource); + } + else { + emitPos(pos); + } + } + + /** + * Emits a token of a node with possible leading and trailing source maps. + * + * @param node The node containing the token. + * @param token The token to emit. + * @param tokenStartPos The start pos of the token. + * @param emitCallback The callback used to emit the token. + */ + function emitTokenWithSourceMap(node: Node | undefined, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { + if (sourceMapsDisabled || node && isInJsonFile(node)) { + return emitCallback(token, writer, tokenPos); + } + + const emitNode = node && node.emitNode; + const emitFlags = emitNode && emitNode.flags || EmitFlags.None; + const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; + const source = range && range.source || sourceMapSource; + + tokenPos = skipSourceTrivia(source, range ? range.pos : tokenPos); + if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } + + tokenPos = emitCallback(token, writer, tokenPos); + + if (range) tokenPos = range.end; + if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { + emitSourcePos(source, tokenPos); + } + + return tokenPos; + } + + function setSourceMapSource(source: SourceMapSource) { + if (sourceMapsDisabled) { + return; + } + + sourceMapSource = source; + + if (isJsonSourceMapSource(source)) { + return; + } + + sourceMapSourceIndex = sourceMapGenerator!.addSource(source.fileName); + if (printerOptions.inlineSources) { + sourceMapGenerator!.setSourceContent(sourceMapSourceIndex, source.text); + } + } + + function isJsonSourceMapSource(sourceFile: SourceMapSource) { + return fileExtensionIs(sourceFile.fileName, Extension.Json); + } } function createBracketsMap() { diff --git a/src/compiler/performance.ts b/src/compiler/performance.ts index 64709a12ba..3471ecd059 100644 --- a/src/compiler/performance.ts +++ b/src/compiler/performance.ts @@ -19,6 +19,41 @@ namespace ts.performance { let marks: Map; let measures: Map; + export interface Timer { + enter(): void; + exit(): void; + } + + export function createTimerIf(condition: boolean, measureName: string, startMarkName: string, endMarkName: string) { + return condition ? createTimer(measureName, startMarkName, endMarkName) : nullTimer; + } + + export function createTimer(measureName: string, startMarkName: string, endMarkName: string): Timer { + let enterCount = 0; + return { + enter, + exit + }; + + function enter() { + if (++enterCount === 1) { + mark(startMarkName); + } + } + + function exit() { + if (--enterCount === 0) { + mark(endMarkName); + measure(measureName, startMarkName, endMarkName); + } + else if (enterCount < 0) { + Debug.fail("enter/exit count does not match."); + } + } + } + + export const nullTimer: Timer = { enter: noop, exit: noop }; + /** * Marks a performance event. * diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index eff543849c..76cf24e51d 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -1,595 +1,522 @@ /* @internal */ namespace ts { - export interface SourceMapWriter { - /** - * Initialize the SourceMapWriter for a new output file. - * - * @param filePath The path to the generated output file. - * @param sourceMapFilePath The path to the output source map file. - * @param sourceFileOrBundle The input source file or bundle for the program. - */ - initialize(filePath: string, sourceMapFilePath: string | undefined, sourceFileOrBundle: SourceFile | Bundle, sourceMapOutput?: SourceMapData[]): void; - - /** - * Reset the SourceMapWriter to an empty state. - */ - reset(): void; - - /** - * Set the current source file. - * - * @param sourceFile The source file. - */ - setSourceFile(sourceFile: SourceMapSource): void; - - /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. - */ - emitPos(pos: number): void; - - /** - * Emits a node with possible leading and trailing source maps. - * - * @param hint The current emit context - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void): void; - - /** - * Emits a token of a node node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number): number; - - /** - * Gets the text for the source map. - */ - getText(): string; - - /** - * Gets the SourceMappingURL for the source map. - */ - getSourceMappingURL(): string; - } - - // Used for initialize lastEncodedSourceMapSpan and reset lastEncodedSourceMapSpan when updateLastEncodedAndRecordedSpans - const defaultLastEncodedSourceMapSpan: SourceMapSpan = { - emittedLine: 0, - emittedColumn: 0, - sourceLine: 0, - sourceColumn: 0, - sourceIndex: 0 - }; - - export interface SourceMapOptions { - sourceMap?: boolean; - inlineSourceMap?: boolean; - inlineSources?: boolean; - sourceRoot?: string; - mapRoot?: string; + export interface SourceMapGeneratorOptions { extendedDiagnostics?: boolean; } - export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter, compilerOptions: SourceMapOptions = host.getCompilerOptions()): SourceMapWriter { - const extendedDiagnostics = compilerOptions.extendedDiagnostics; - let currentSource: SourceMapSource; - let currentSourceText: string; - let sourceMapDir: string; // The directory in which sourcemap will be + export function createSourceMapGenerator(host: EmitHost, file: string, sourceRoot: string, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { + const { enter, exit } = generatorOptions.extendedDiagnostics + ? performance.createTimer("Source Map", "beforeSourcemap", "afterSourcemap") + : performance.nullTimer; // Current source map file and its index in the sources list - let sourceMapSourceIndex: number; + const rawSources: string[] = []; + const sources: string[] = []; + const sourceToSourceIndexMap = createMap(); + let sourcesContent: (string | null)[] | undefined; - // Last recorded and encoded spans - let lastRecordedSourceMapSpan: SourceMapSpan | undefined; - let lastEncodedSourceMapSpan: SourceMapSpan | undefined; - let lastEncodedNameIndex: number | undefined; + const names: string[] = []; + let nameToNameIndexMap: Map | undefined; + let mappings = ""; - // Source map data - let sourceMapData: SourceMapData; - let sourceMapDataList: SourceMapData[] | undefined; - let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap); + // Last recorded and encoded mappings + let lastGeneratedLine = 0; + let lastGeneratedCharacter = 0; + let lastSourceIndex = 0; + let lastSourceLine = 0; + let lastSourceCharacter = 0; + let lastNameIndex = 0; + let hasLast = false; + + let pendingGeneratedLine = 0; + let pendingGeneratedCharacter = 0; + let pendingSourceIndex = 0; + let pendingSourceLine = 0; + let pendingSourceCharacter = 0; + let pendingNameIndex = 0; + let hasPending = false; + let hasPendingSource = false; + let hasPendingName = false; return { - initialize, - reset, - setSourceFile, - emitPos, - emitNodeWithSourceMap, - emitTokenWithSourceMap, - getText, - getSourceMappingURL, + getSources: () => rawSources, + addSource, + setSourceContent, + addName, + addMapping, + appendSourceMap, + toJSON, + toString: () => JSON.stringify(toJSON()) }; - /** - * Skips trivia such as comments and white-space that can optionally overriden by the source map source - */ - function skipSourceTrivia(pos: number): number { - return currentSource.skipTrivia ? currentSource.skipTrivia(pos) : skipTrivia(currentSourceText, pos); + function addSource(fileName: string) { + enter(); + const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, + fileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + + let sourceIndex = sourceToSourceIndexMap.get(source); + if (sourceIndex === undefined) { + sourceIndex = sources.length; + sources.push(source); + rawSources.push(fileName); + sourceToSourceIndexMap.set(source, sourceIndex); + } + exit(); + return sourceIndex; } - /** - * Initialize the SourceMapWriter for a new output file. - * - * @param filePath The path to the generated output file. - * @param sourceMapFilePath The path to the output source map file. - * @param sourceFileOrBundle The input source file or bundle for the program. - */ - function initialize(filePath: string, sourceMapFilePath: string, sourceFileOrBundle: SourceFile | Bundle, outputSourceMapDataList?: SourceMapData[]) { - if (disabled || fileExtensionIs(filePath, Extension.Json)) { + function setSourceContent(sourceIndex: number, content: string | null) { + enter(); + if (content !== null) { + if (!sourcesContent) sourcesContent = []; + while (sourcesContent.length < sourceIndex) { + // tslint:disable-next-line:no-null-keyword boolean-trivia + sourcesContent.push(null); + } + sourcesContent[sourceIndex] = content; + } + exit(); + } + + function addName(name: string) { + enter(); + if (!nameToNameIndexMap) nameToNameIndexMap = createMap(); + let nameIndex = nameToNameIndexMap.get(name); + if (nameIndex === undefined) { + nameIndex = names.length; + names.push(name); + nameToNameIndexMap.set(name, nameIndex); + } + exit(); + return nameIndex; + } + + function isNewGeneratedPosition(generatedLine: number, generatedCharacter: number) { + return !hasPending + || pendingGeneratedLine !== generatedLine + || pendingGeneratedCharacter !== generatedCharacter; + } + + function isBacktrackingSourcePosition(sourceIndex: number | undefined, sourceLine: number | undefined, sourceCharacter: number | undefined) { + return sourceIndex !== undefined + && sourceLine !== undefined + && sourceCharacter !== undefined + && pendingSourceIndex === sourceIndex + && (pendingSourceLine > sourceLine + || pendingSourceLine === sourceLine && pendingSourceCharacter > sourceCharacter); + } + + function addMapping(generatedLine: number, generatedCharacter: number, sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + Debug.assert(sourceIndex === undefined || sourceIndex >= 0, "sourceIndex cannot be negative"); + Debug.assert(sourceLine === undefined || sourceLine >= 0, "sourceLine cannot be negative"); + Debug.assert(sourceCharacter === undefined || sourceCharacter >= 0, "sourceCharacter cannot be negative"); + enter(); + // If this location wasn't recorded or the location in source is going backwards, record the mapping + if (isNewGeneratedPosition(generatedLine, generatedCharacter) || + isBacktrackingSourcePosition(sourceIndex, sourceLine, sourceCharacter)) { + commitPendingMapping(); + pendingGeneratedLine = generatedLine; + pendingGeneratedCharacter = generatedCharacter; + hasPendingSource = false; + hasPendingName = false; + hasPending = true; + } + + if (sourceIndex !== undefined && sourceLine !== undefined && sourceCharacter !== undefined) { + pendingSourceIndex = sourceIndex; + pendingSourceLine = sourceLine; + pendingSourceCharacter = sourceCharacter; + hasPendingSource = true; + if (nameIndex !== undefined) { + pendingNameIndex = nameIndex; + hasPendingName = true; + } + } + exit(); + } + + function appendSourceMap(generatedLine: number, generatedCharacter: number, map: RawSourceMap, sourceMapPath: string) { + Debug.assert(generatedLine >= pendingGeneratedLine, "generatedLine cannot backtrack"); + Debug.assert(generatedCharacter >= 0, "generatedCharacter cannot be negative"); + enter(); + // First, decode the old component sourcemap + const sourceIndexToNewSourceIndexMap: number[] = []; + let nameIndexToNewNameIndexMap: number[] | undefined; + const mappingIterator = decodeMappings(map.mappings); + for (let { value: raw, done } = mappingIterator.next(); !done; { value: raw, done } = mappingIterator.next()) { + // Then reencode all the updated mappings into the overall map + let newSourceIndex: number | undefined; + let newSourceLine: number | undefined; + let newSourceCharacter: number | undefined; + let newNameIndex: number | undefined; + if (raw.sourceIndex !== undefined) { + newSourceIndex = sourceIndexToNewSourceIndexMap[raw.sourceIndex]; + if (newSourceIndex === undefined) { + // Apply offsets to each position and fixup source entries + const rawPath = map.sources[raw.sourceIndex]; + const relativePath = map.sourceRoot ? combinePaths(map.sourceRoot, rawPath) : rawPath; + const combinedPath = combinePaths(getDirectoryPath(sourceMapPath), relativePath); + sourceIndexToNewSourceIndexMap[raw.sourceIndex] = newSourceIndex = addSource(combinedPath); + if (map.sourcesContent && typeof map.sourcesContent[raw.sourceIndex] === "string") { + setSourceContent(newSourceIndex, map.sourcesContent[raw.sourceIndex]); + } + } + + newSourceLine = raw.sourceLine; + newSourceCharacter = raw.sourceCharacter; + if (map.names && raw.nameIndex !== undefined) { + if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; + newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; + if (newNameIndex === undefined) { + nameIndexToNewNameIndexMap[raw.nameIndex] = newNameIndex = addName(map.names[raw.nameIndex]); + } + } + } + + const newGeneratedLine = raw.generatedLine + generatedLine; + const newGeneratedCharacter = raw.generatedLine === 0 ? raw.generatedCharacter + generatedCharacter : raw.generatedCharacter; + addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); + } + exit(); + } + + function shouldCommitMapping() { + return !hasLast + || lastGeneratedLine !== pendingGeneratedLine + || lastGeneratedCharacter !== pendingGeneratedCharacter + || lastSourceIndex !== pendingSourceIndex + || lastSourceLine !== pendingSourceLine + || lastSourceCharacter !== pendingSourceCharacter + || lastNameIndex !== pendingNameIndex; + } + + function commitPendingMapping() { + if (!hasPending || !shouldCommitMapping()) { return; } - if (sourceMapData) { - reset(); - } - sourceMapDataList = outputSourceMapDataList; + enter(); - currentSource = undefined!; - currentSourceText = undefined!; - - // Current source map file and its index in the sources list - sourceMapSourceIndex = -1; - - // Last recorded and encoded spans - lastRecordedSourceMapSpan = undefined; - lastEncodedSourceMapSpan = defaultLastEncodedSourceMapSpan; - lastEncodedNameIndex = 0; - - // Initialize source map data - sourceMapData = { - sourceMapFilePath, - jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined!, // TODO: GH#18217 - sourceMapFile: getBaseFileName(normalizeSlashes(filePath)), - sourceMapSourceRoot: compilerOptions.sourceRoot || "", - sourceMapSources: [], - inputSourceFileNames: [], - sourceMapNames: [], - sourceMapMappings: "", - sourceMapSourcesContent: compilerOptions.inlineSources ? [] : undefined, - }; - - // Normalize source root and make sure it has trailing "/" so that it can be used to combine paths with the - // relative paths of the sources list in the sourcemap - sourceMapData.sourceMapSourceRoot = normalizeSlashes(sourceMapData.sourceMapSourceRoot); - if (sourceMapData.sourceMapSourceRoot.length && sourceMapData.sourceMapSourceRoot.charCodeAt(sourceMapData.sourceMapSourceRoot.length - 1) !== CharacterCodes.slash) { - sourceMapData.sourceMapSourceRoot += directorySeparator; - } - - if (compilerOptions.mapRoot) { - sourceMapDir = normalizeSlashes(compilerOptions.mapRoot); - if (sourceFileOrBundle.kind === SyntaxKind.SourceFile) { // emitting single module file - // For modules or multiple emit files the mapRoot will have directory structure like the sources - // So if src\a.ts and src\lib\b.ts are compiled together user would be moving the maps into mapRoot\a.js.map and mapRoot\lib\b.js.map - sourceMapDir = getDirectoryPath(getSourceFilePathInNewDir(sourceFileOrBundle.fileName, host, sourceMapDir)); - } - - if (!isRootedDiskPath(sourceMapDir) && !isUrl(sourceMapDir)) { - // The relative paths are relative to the common directory - sourceMapDir = combinePaths(host.getCommonSourceDirectory(), sourceMapDir); - sourceMapData.jsSourceMappingURL = getRelativePathToDirectoryOrUrl( - getDirectoryPath(normalizePath(filePath)), // get the relative sourceMapDir path based on jsFilePath - combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL), // this is where user expects to see sourceMap - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - } - else { - sourceMapData.jsSourceMappingURL = combinePaths(sourceMapDir, sourceMapData.jsSourceMappingURL); + // Line/Comma delimiters + if (lastGeneratedLine < pendingGeneratedLine) { + // Emit line delimiters + do { + mappings += ";"; + lastGeneratedLine++; + lastGeneratedCharacter = 0; } + while (lastGeneratedLine < pendingGeneratedLine); } else { - sourceMapDir = getDirectoryPath(normalizePath(filePath)); - } - } - - /** - * Reset the SourceMapWriter to an empty state. - */ - function reset() { - if (disabled) { - return; + Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); + // Emit comma to separate the entry + if (hasLast) { + mappings += ","; + } } - // Record source map data for the test harness. - if (sourceMapDataList) { - sourceMapDataList.push(sourceMapData); + // 1. Relative generated character + mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter); + lastGeneratedCharacter = pendingGeneratedCharacter; + + if (hasPendingSource) { + // 2. Relative sourceIndex + mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex); + lastSourceIndex = pendingSourceIndex; + + // 3. Relative source line + mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine); + lastSourceLine = pendingSourceLine; + + // 4. Relative source character + mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter); + lastSourceCharacter = pendingSourceCharacter; + + if (hasPendingName) { + // 5. Relative nameIndex + mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex); + lastNameIndex = pendingNameIndex; + } } - currentSource = undefined!; - sourceMapDir = undefined!; - sourceMapSourceIndex = undefined!; - lastRecordedSourceMapSpan = undefined; - lastEncodedSourceMapSpan = undefined!; - lastEncodedNameIndex = undefined; - sourceMapData = undefined!; - sourceMapDataList = undefined!; + hasLast = true; + exit(); } - type SourceMapSectionDefinition = - | { offset: { line: number, column: number }, url: string } // Included for completeness - | { offset: { line: number, column: number }, map: SourceMap }; - - interface SectionalSourceMap { - version: 3; - file: string; - sections: SourceMapSectionDefinition[]; - } - - type SourceMap = SectionalSourceMap | SourceMapSection; - - function captureSection(): SourceMapSection { + function toJSON(): RawSourceMap { + commitPendingMapping(); return { version: 3, - file: sourceMapData.sourceMapFile, - sourceRoot: sourceMapData.sourceMapSourceRoot, - sources: sourceMapData.sourceMapSources, - names: sourceMapData.sourceMapNames, - mappings: sourceMapData.sourceMapMappings, - sourcesContent: sourceMapData.sourceMapSourcesContent, + file, + sourceRoot, + sources, + names, + mappings, + sourcesContent, + }; + } + } + + // Sometimes tools can see the following line as a source mapping url comment, so we mangle it a bit (the [M]) + const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/; + const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; + + /** + * Tries to find the sourceMappingURL comment at the end of a file. + * @param text The source text of the file. + * @param lineStarts The line starts of the file. + */ + export function tryGetSourceMappingURL(text: string, lineStarts: ReadonlyArray = computeLineStarts(text)) { + for (let index = lineStarts.length - 1; index >= 0; index--) { + const line = text.substring(lineStarts[index], lineStarts[index + 1]); + const comment = sourceMapCommentRegExp.exec(line); + if (comment) { + return comment[1]; + } + // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file + else if (!line.match(whitespaceOrMapCommentRegExp)) { + break; + } + } + } + + function isStringOrNull(x: any) { + // tslint:disable-next-line:no-null-keyword + return typeof x === "string" || x === null; + } + + export function isRawSourceMap(x: any): x is RawSourceMap { + // tslint:disable-next-line:no-null-keyword + return x !== null + && typeof x === "object" + && x.version === 3 + && typeof x.file === "string" + && typeof x.mappings === "string" + && isArray(x.sources) && every(x.sources, isString) + && (x.sourceRoot === undefined || x.sourceRoot === null || typeof x.sourceRoot === "string") + && (x.sourcesContent === undefined || x.sourcesContent === null || isArray(x.sourcesContent) && every(x.sourcesContent, isStringOrNull)) + && (x.names === undefined || x.names === null || isArray(x.names) && every(x.names, isString)); + } + + export function tryParseRawSourceMap(text: string) { + try { + const parsed = JSON.parse(text); + if (isRawSourceMap(parsed)) { + return parsed; + } + } + catch { + // empty + } + + return undefined; + } + + export interface MappingsDecoder extends Iterator { + readonly pos: number; + readonly error: string | undefined; + readonly state: Required; + } + + export interface Mapping { + generatedLine: number; + generatedCharacter: number; + sourceIndex?: number; + sourceLine?: number; + sourceCharacter?: number; + nameIndex?: number; + } + + export interface SourceMapping extends Mapping { + sourceIndex: number; + sourceLine: number; + sourceCharacter: number; + } + + export function decodeMappings(mappings: string): MappingsDecoder { + let done = false; + let pos = 0; + let generatedLine = 0; + let generatedCharacter = 0; + let sourceIndex = 0; + let sourceLine = 0; + let sourceCharacter = 0; + let nameIndex = 0; + let error: string | undefined; + + return { + get pos() { return pos; }, + get error() { return error; }, + get state() { return captureMapping(/*hasSource*/ true, /*hasName*/ true); }, + next() { + while (!done && pos < mappings.length) { + const ch = mappings.charCodeAt(pos); + if (ch === CharacterCodes.semicolon) { + // new line + generatedLine++; + generatedCharacter = 0; + pos++; + continue; + } + + if (ch === CharacterCodes.comma) { + // Next entry is on same line - no action needed + pos++; + continue; + } + + let hasSource = false; + let hasName = false; + + generatedCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (generatedCharacter < 0) return setErrorAndStopIterating("Invalid generatedCharacter found"); + + if (!isSourceMappingSegmentEnd()) { + hasSource = true; + + sourceIndex += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceIndex < 0) return setErrorAndStopIterating("Invalid sourceIndex found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceIndex"); + + sourceLine += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceLine < 0) return setErrorAndStopIterating("Invalid sourceLine found"); + if (isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Format: No entries after sourceLine"); + + sourceCharacter += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (sourceCharacter < 0) return setErrorAndStopIterating("Invalid sourceCharacter found"); + + if (!isSourceMappingSegmentEnd()) { + hasName = true; + nameIndex += base64VLQFormatDecode(); + if (hasReportedError()) return stopIterating(); + if (nameIndex < 0) return setErrorAndStopIterating("Invalid nameIndex found"); + + if (!isSourceMappingSegmentEnd()) return setErrorAndStopIterating("Unsupported Error Format: Entries after nameIndex"); + } + } + + return { value: captureMapping(hasSource, hasName), done }; + } + + return stopIterating(); + } + }; + + function captureMapping(hasSource: true, hasName: true): Required; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping; + function captureMapping(hasSource: boolean, hasName: boolean): Mapping { + return { + generatedLine, + generatedCharacter, + sourceIndex: hasSource ? sourceIndex : undefined, + sourceLine: hasSource ? sourceLine : undefined, + sourceCharacter: hasSource ? sourceCharacter : undefined, + nameIndex: hasName ? nameIndex : undefined }; } + function stopIterating(): { value: never, done: true } { + done = true; + return { value: undefined!, done: true }; + } - // Encoding for sourcemap span - function encodeLastRecordedSourceMapSpan() { - if (!lastRecordedSourceMapSpan || lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) { - return; + function setError(message: string) { + if (error === undefined) { + error = message; + } + } + + function setErrorAndStopIterating(message: string) { + setError(message); + return stopIterating(); + } + + function hasReportedError() { + return error !== undefined; + } + + function isSourceMappingSegmentEnd() { + return (pos === mappings.length || + mappings.charCodeAt(pos) === CharacterCodes.comma || + mappings.charCodeAt(pos) === CharacterCodes.semicolon); + } + + function base64VLQFormatDecode(): number { + let moreDigits = true; + let shiftCount = 0; + let value = 0; + + for (; moreDigits; pos++) { + if (pos >= mappings.length) return setError("Error in decoding base64VLQFormatDecode, past the mapping string"), -1; + + // 6 digit number + const currentByte = base64FormatDecode(mappings.charCodeAt(pos)); + if (currentByte === -1) return setError("Invalid character in VLQ"), -1; + + // If msb is set, we still have more bits to continue + moreDigits = (currentByte & 32) !== 0; + + // least significant 5 bits are the next msbs in the final value. + value = value | ((currentByte & 31) << shiftCount); + shiftCount += 5; } - Debug.assert(lastRecordedSourceMapSpan.emittedColumn >= 0, "lastEncodedSourceMapSpan.emittedColumn was negative"); - Debug.assert(lastRecordedSourceMapSpan.sourceIndex >= 0, "lastEncodedSourceMapSpan.sourceIndex was negative"); - Debug.assert(lastRecordedSourceMapSpan.sourceLine >= 0, "lastEncodedSourceMapSpan.sourceLine was negative"); - Debug.assert(lastRecordedSourceMapSpan.sourceColumn >= 0, "lastEncodedSourceMapSpan.sourceColumn was negative"); - - let prevEncodedEmittedColumn = lastEncodedSourceMapSpan!.emittedColumn; - // Line/Comma delimiters - if (lastEncodedSourceMapSpan!.emittedLine === lastRecordedSourceMapSpan.emittedLine) { - // Emit comma to separate the entry - if (sourceMapData.sourceMapMappings) { - sourceMapData.sourceMapMappings += ","; - } + // Least significant bit if 1 represents negative and rest of the msb is actual absolute value + if ((value & 1) === 0) { + // + number + value = value >> 1; } else { - // Emit line delimiters - for (let encodedLine = lastEncodedSourceMapSpan!.emittedLine; encodedLine < lastRecordedSourceMapSpan.emittedLine; encodedLine++) { - sourceMapData.sourceMapMappings += ";"; - } - prevEncodedEmittedColumn = 0; + // - number + value = value >> 1; + value = -value; } - // 1. Relative Column 0 based - sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.emittedColumn - prevEncodedEmittedColumn); - - // 2. Relative sourceIndex - sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceIndex - lastEncodedSourceMapSpan!.sourceIndex); - - // 3. Relative sourceLine 0 based - sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceLine - lastEncodedSourceMapSpan!.sourceLine); - - // 4. Relative sourceColumn 0 based - sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceColumn - lastEncodedSourceMapSpan!.sourceColumn); - - // 5. Relative namePosition 0 based - if (lastRecordedSourceMapSpan.nameIndex! >= 0) { - Debug.assert(false, "We do not support name index right now, Make sure to update updateLastEncodedAndRecordedSpans when we start using this"); - sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.nameIndex! - lastEncodedNameIndex!); - lastEncodedNameIndex = lastRecordedSourceMapSpan.nameIndex; - } - - lastEncodedSourceMapSpan = lastRecordedSourceMapSpan; - } - - /** - * Emits a mapping. - * - * If the position is synthetic (undefined or a negative value), no mapping will be - * created. - * - * @param pos The position. - */ - function emitPos(pos: number) { - if (disabled || positionIsSynthesized(pos) || isJsonSourceMapSource(currentSource)) { - return; - } - - if (extendedDiagnostics) { - performance.mark("beforeSourcemap"); - } - - const sourceLinePos = getLineAndCharacterOfPosition(currentSource, pos); - - const emittedLine = writer.getLine(); - const emittedColumn = writer.getColumn(); - - // If this location wasn't recorded or the location in source is going backwards, record the span - if (!lastRecordedSourceMapSpan || - lastRecordedSourceMapSpan.emittedLine !== emittedLine || - lastRecordedSourceMapSpan.emittedColumn !== emittedColumn || - (lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex && - (lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line || - (lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) { - - // Encode the last recordedSpan before assigning new - encodeLastRecordedSourceMapSpan(); - - // New span - lastRecordedSourceMapSpan = { - emittedLine, - emittedColumn, - sourceLine: sourceLinePos.line, - sourceColumn: sourceLinePos.character, - sourceIndex: sourceMapSourceIndex - }; - } - else { - // Take the new pos instead since there is no change in emittedLine and column since last location - lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line; - lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character; - lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex; - } - - if (extendedDiagnostics) { - performance.mark("afterSourcemap"); - performance.measure("Source Map", "beforeSourcemap", "afterSourcemap"); - } - } - - function isPossiblySourceMap(x: {}): x is SourceMapSection { - return typeof x === "object" && !!(x as any).mappings && typeof (x as any).mappings === "string" && !!(x as any).sources; - } - - /** - * Emits a node with possible leading and trailing source maps. - * - * @param hint A hint as to the intended usage of the node. - * @param node The node to emit. - * @param emitCallback The callback used to emit the node. - */ - function emitNodeWithSourceMap(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { - if (disabled || isInJsonFile(node)) { - return emitCallback(hint, node); - } - - if (node) { - if (isUnparsedSource(node) && node.sourceMapText !== undefined) { - const text = node.sourceMapText; - let parsed: {} | undefined; - try { - parsed = JSON.parse(text); - } - catch { - // empty - } - if (!parsed || !isPossiblySourceMap(parsed)) { - return emitCallback(hint, node); - } - const offsetLine = writer.getLine(); - const firstLineColumnOffset = writer.getColumn(); - // First, decode the old component sourcemap - const originalMap = parsed; - - const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir; - const resolvedPathCache = createMap(); - const absolutePathCache = createMap(); - const sourcemapIterator = sourcemaps.decodeMappings(originalMap); - for (let { value: raw, done } = sourcemapIterator.next(); !done; { value: raw, done } = sourcemapIterator.next()) { - const pathCacheKey = "" + raw.sourceIndex; - // Apply offsets to each position and fixup source entries - if (!resolvedPathCache.has(pathCacheKey)) { - const rawPath = originalMap.sources[raw.sourceIndex]; - const relativePath = originalMap.sourceRoot ? combinePaths(originalMap.sourceRoot, rawPath) : rawPath; - const combinedPath = combinePaths(getDirectoryPath(node.sourceMapPath!), relativePath); - const resolvedPath = getRelativePathToDirectoryOrUrl( - sourcesDirectoryPath, - combinedPath, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true - ); - resolvedPathCache.set(pathCacheKey, resolvedPath); - absolutePathCache.set(pathCacheKey, getNormalizedAbsolutePath(resolvedPath, sourcesDirectoryPath)); - } - const resolvedPath = resolvedPathCache.get(pathCacheKey)!; - const absolutePath = absolutePathCache.get(pathCacheKey)!; - // tslint:disable-next-line:no-null-keyword - setupSourceEntry(absolutePath, originalMap.sourcesContent ? originalMap.sourcesContent[raw.sourceIndex] : null, resolvedPath); // TODO: Lookup content for inlining? - const newIndex = sourceMapData.sourceMapSources.indexOf(resolvedPath); - // Then reencode all the updated spans into the overall map - encodeLastRecordedSourceMapSpan(); - lastRecordedSourceMapSpan = { - ...raw, - emittedLine: raw.emittedLine + offsetLine, - emittedColumn: raw.emittedLine === 0 ? (raw.emittedColumn + firstLineColumnOffset) : raw.emittedColumn, - sourceIndex: newIndex, - }; - } - // And actually emit the text these sourcemaps are for - return emitCallback(hint, node); - } - const emitNode = node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.sourceMapRange; - const { pos, end } = range || node; - let source = range && range.source; - const oldSource = currentSource; - if (source === oldSource) source = undefined; - - if (source) setSourceFile(source); - - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 - && pos >= 0) { - emitPos(skipSourceTrivia(pos)); - } - - if (source) setSourceFile(oldSource); - - if (emitFlags & EmitFlags.NoNestedSourceMaps) { - disabled = true; - emitCallback(hint, node); - disabled = false; - } - else { - emitCallback(hint, node); - } - - if (source) setSourceFile(source); - - if (node.kind !== SyntaxKind.NotEmittedStatement - && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 - && end >= 0) { - emitPos(end); - } - - if (source) setSourceFile(oldSource); - } - } - - /** - * Emits a token of a node with possible leading and trailing source maps. - * - * @param node The node containing the token. - * @param token The token to emit. - * @param tokenStartPos The start pos of the token. - * @param emitCallback The callback used to emit the token. - */ - function emitTokenWithSourceMap(node: Node, token: SyntaxKind, writer: (s: string) => void, tokenPos: number, emitCallback: (token: SyntaxKind, writer: (s: string) => void, tokenStartPos: number) => number) { - if (disabled || isInJsonFile(node)) { - return emitCallback(token, writer, tokenPos); - } - - const emitNode = node && node.emitNode; - const emitFlags = emitNode && emitNode.flags || EmitFlags.None; - const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - - tokenPos = skipSourceTrivia(range ? range.pos : tokenPos); - if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { - emitPos(tokenPos); - } - - tokenPos = emitCallback(token, writer, tokenPos); - - if (range) tokenPos = range.end; - if ((emitFlags & EmitFlags.NoTokenTrailingSourceMaps) === 0 && tokenPos >= 0) { - emitPos(tokenPos); - } - - return tokenPos; - } - - function isJsonSourceMapSource(sourceFile: SourceMapSource) { - return fileExtensionIs(sourceFile.fileName, Extension.Json); - } - - /** - * Set the current source file. - * - * @param sourceFile The source file. - */ - function setSourceFile(sourceFile: SourceMapSource) { - if (disabled) { - return; - } - - currentSource = sourceFile; - currentSourceText = currentSource.text; - - if (isJsonSourceMapSource(sourceFile)) { - return; - } - - setupSourceEntry(sourceFile.fileName, sourceFile.text); - } - - function setupSourceEntry(fileName: string, content: string | null, source?: string) { - if (!source) { - // Add the file to tsFilePaths - // If sourceroot option: Use the relative path corresponding to the common directory path - // otherwise source locations relative to map file location - const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir; - - source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, - fileName, - host.getCurrentDirectory(), - host.getCanonicalFileName, - /*isAbsolutePathAnUrl*/ true); - } - - sourceMapSourceIndex = sourceMapData.sourceMapSources.indexOf(source); - if (sourceMapSourceIndex === -1) { - sourceMapSourceIndex = sourceMapData.sourceMapSources.length; - sourceMapData.sourceMapSources.push(source); - - // The one that can be used from program to get the actual source file - sourceMapData.inputSourceFileNames.push(fileName); - - if (compilerOptions.inlineSources) { - sourceMapData.sourceMapSourcesContent!.push(content); - } - } - } - - /** - * Gets the text for the source map. - */ - function getText() { - if (disabled || isJsonSourceMapSource(currentSource)) { - return undefined!; // TODO: GH#18217 - } - - encodeLastRecordedSourceMapSpan(); - - return JSON.stringify(captureSection()); - } - - /** - * Gets the SourceMappingURL for the source map. - */ - function getSourceMappingURL() { - if (disabled || isJsonSourceMapSource(currentSource)) { - return undefined!; // TODO: GH#18217 - } - - if (compilerOptions.inlineSourceMap) { - // Encode the sourceMap into the sourceMap url - const base64SourceMapText = base64encode(sys, getText()); - return sourceMapData.jsSourceMappingURL = `data:application/json;base64,${base64SourceMapText}`; - } - else { - return sourceMapData.jsSourceMappingURL; - } + return value; } } - export interface SourceMapSection { - version: 3; - file: string; - sourceRoot?: string; - sources: string[]; - names?: string[]; - mappings: string; - sourcesContent?: (string | null)[]; - sections?: undefined; + export function sameMapping(left: T, right: T) { + return left === right + || left.generatedLine === right.generatedLine + && left.generatedCharacter === right.generatedCharacter + && left.sourceIndex === right.sourceIndex + && left.sourceLine === right.sourceLine + && left.sourceCharacter === right.sourceCharacter + && left.nameIndex === right.nameIndex; } - const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { + return mapping.sourceIndex !== undefined + && mapping.sourceLine !== undefined + && mapping.sourceCharacter !== undefined; + } - function base64FormatEncode(inValue: number) { - if (inValue < 64) { - return base64Chars.charAt(inValue); - } + function base64FormatEncode(value: number) { + return value >= 0 && value < 26 ? CharacterCodes.A + value : + value >= 26 && value < 52 ? CharacterCodes.a + value - 26 : + value >= 52 && value < 62 ? CharacterCodes._0 + value - 52 : + value === 62 ? CharacterCodes.plus : + value === 63 ? CharacterCodes.slash : + Debug.fail(`${value}: not a base64 value`); + } - throw TypeError(inValue + ": not a 64 based value"); + function base64FormatDecode(ch: number) { + return ch >= CharacterCodes.A && ch <= CharacterCodes.Z ? ch - CharacterCodes.A : + ch >= CharacterCodes.a && ch <= CharacterCodes.z ? ch - CharacterCodes.a + 26 : + ch >= CharacterCodes._0 && ch <= CharacterCodes._9 ? ch - CharacterCodes._0 + 52 : + ch === CharacterCodes.plus ? 62 : + ch === CharacterCodes.slash ? 63 : + -1; } function base64VLQFormatEncode(inValue: number) { @@ -614,9 +541,178 @@ namespace ts { // There are still more digits to decode, set the msb (6th bit) currentDigit = currentDigit | 32; } - encodedStr = encodedStr + base64FormatEncode(currentDigit); + encodedStr = encodedStr + String.fromCharCode(base64FormatEncode(currentDigit)); } while (inValue > 0); return encodedStr; } + + interface MappedPosition { + generatedPosition: number; + source: string | undefined; + sourceIndex: number | undefined; + sourcePosition: number | undefined; + nameIndex: number | undefined; + } + + interface SourceMappedPosition extends MappedPosition { + source: string; + sourceIndex: number; + sourcePosition: number; + } + + function isSourceMappedPosition(value: MappedPosition): value is SourceMappedPosition { + return value.sourceIndex !== undefined + && value.sourcePosition !== undefined; + } + + function sameMappedPosition(left: MappedPosition, right: MappedPosition) { + return left.generatedPosition === right.generatedPosition + && left.sourceIndex === right.sourceIndex + && left.sourcePosition === right.sourcePosition; + } + + function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + return compareValues(left.sourceIndex, right.sourceIndex); + } + + function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { + return compareValues(left.generatedPosition, right.generatedPosition); + } + + function getSourcePositionOfMapping(value: SourceMappedPosition) { + return value.sourcePosition; + } + + function getGeneratedPositionOfMapping(value: MappedPosition) { + return value.generatedPosition; + } + + export function createDocumentPositionMapper(host: DocumentPositionMapperHost, map: RawSourceMap, mapPath: string): DocumentPositionMapper { + const mapDirectory = getDirectoryPath(mapPath); + const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, mapDirectory) : mapDirectory; + const generatedAbsoluteFilePath = getNormalizedAbsolutePath(map.file, mapDirectory); + const generatedCanonicalFilePath = host.getCanonicalFileName(generatedAbsoluteFilePath) as Path; + const generatedFile = host.getSourceFileLike(generatedCanonicalFilePath); + const sourceFileAbsolutePaths = map.sources.map(source => getNormalizedAbsolutePath(source, sourceRoot)); + const sourceFileCanonicalPaths = sourceFileAbsolutePaths.map(source => host.getCanonicalFileName(source) as Path); + const sourceToSourceIndexMap = createMapFromEntries(sourceFileCanonicalPaths.map((source, i) => [source, i] as [string, number])); + let decodedMappings: ReadonlyArray | undefined; + let generatedMappings: SortedReadonlyArray | undefined; + let sourceMappings: ReadonlyArray> | undefined; + + return { + getSourcePosition, + getGeneratedPosition + }; + + function processMapping(mapping: Mapping): MappedPosition { + const generatedPosition = generatedFile !== undefined + ? getPositionOfLineAndCharacter(generatedFile, mapping.generatedLine, mapping.generatedCharacter) + : -1; + let source: string | undefined; + let sourcePosition: number | undefined; + if (isSourceMapping(mapping)) { + const sourceFilePath = sourceFileCanonicalPaths[mapping.sourceIndex]; + const sourceFile = host.getSourceFileLike(sourceFilePath); + source = map.sources[mapping.sourceIndex]; + sourcePosition = sourceFile !== undefined + ? getPositionOfLineAndCharacter(sourceFile, mapping.sourceLine, mapping.sourceCharacter) + : -1; + } + return { + generatedPosition, + source, + sourceIndex: mapping.sourceIndex, + sourcePosition, + nameIndex: mapping.nameIndex + }; + } + + function getDecodedMappings() { + if (decodedMappings === undefined) { + const decoder = decodeMappings(map.mappings); + const mappings = arrayFrom(decoder, processMapping); + if (decoder.error !== undefined) { + if (host.log) { + host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); + } + decodedMappings = emptyArray; + } + else { + decodedMappings = mappings; + } + } + return decodedMappings; + } + + function getSourceMappings(sourceIndex: number) { + if (sourceMappings === undefined) { + const lists: SourceMappedPosition[][] = []; + for (const mapping of getDecodedMappings()) { + if (!isSourceMappedPosition(mapping)) continue; + let list = lists[mapping.sourceIndex]; + if (!list) lists[mapping.sourceIndex] = list = []; + list.push(mapping); + } + sourceMappings = lists.map(list => sortAndDeduplicate(list, compareSourcePositions, sameMappedPosition)); + } + return sourceMappings[sourceIndex]; + } + + function getGeneratedMappings() { + if (generatedMappings === undefined) { + const list: MappedPosition[] = []; + for (const mapping of getDecodedMappings()) { + list.push(mapping); + } + generatedMappings = sortAndDeduplicate(list, compareGeneratedPositions, sameMappedPosition); + } + return generatedMappings; + } + + function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { + const sourceIndex = sourceToSourceIndexMap.get(host.getCanonicalFileName(loc.fileName)); + if (sourceIndex === undefined) return loc; + + const sourceMappings = getSourceMappings(sourceIndex); + if (!some(sourceMappings)) return loc; + + let targetIndex = binarySearchKey(sourceMappings, loc.pos, getSourcePositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } + + const mapping = sourceMappings[targetIndex]; + if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { + return loc; + } + + return { fileName: generatedAbsoluteFilePath, pos: mapping.generatedPosition }; // Closest pos + } + + function getSourcePosition(loc: DocumentPosition): DocumentPosition { + const generatedMappings = getGeneratedMappings(); + if (!some(generatedMappings)) return loc; + + let targetIndex = binarySearchKey(generatedMappings, loc.pos, getGeneratedPositionOfMapping, compareValues); + if (targetIndex < 0) { + // if no exact match, closest is 2's complement of result + targetIndex = ~targetIndex; + } + + const mapping = generatedMappings[targetIndex]; + if (mapping === undefined || !isSourceMappedPosition(mapping)) { + return loc; + } + + return { fileName: sourceFileAbsolutePaths[mapping.sourceIndex], pos: mapping.sourcePosition }; // Closest pos + } + } + + export const identitySourceMapConsumer: DocumentPositionMapper = { + getSourcePosition: identity, + getGeneratedPosition: identity + }; } diff --git a/src/compiler/sourcemapDecoder.ts b/src/compiler/sourcemapDecoder.ts deleted file mode 100644 index 9045f25b5f..0000000000 --- a/src/compiler/sourcemapDecoder.ts +++ /dev/null @@ -1,376 +0,0 @@ -/* @internal */ -namespace ts { - export interface SourceFileLikeCache { - get(path: Path): SourceFileLike | undefined; - } - - export function createSourceFileLikeCache(host: { readFile?: (path: string) => string | undefined, fileExists?: (path: string) => boolean }): SourceFileLikeCache { - const cached = createMap(); - return { - get(path: Path) { - if (cached.has(path)) { - return cached.get(path); - } - if (!host.fileExists || !host.readFile || !host.fileExists(path)) return; - // And failing that, check the disk - const text = host.readFile(path)!; // TODO: GH#18217 - const file = { - text, - lineMap: undefined, - getLineAndCharacterOfPosition(pos: number) { - return computeLineAndCharacterOfPosition(getLineStarts(this), pos); - } - } as SourceFileLike; - cached.set(path, file); - return file; - } - }; - } -} - -/* @internal */ -namespace ts.sourcemaps { - export interface SourceMapData { - version?: number; - file?: string; - sourceRoot?: string; - sources: string[]; - sourcesContent?: (string | null)[]; - names?: string[]; - mappings: string; - } - - export interface SourceMappableLocation { - fileName: string; - position: number; - } - - export interface SourceMapper { - getOriginalPosition(input: SourceMappableLocation): SourceMappableLocation; - getGeneratedPosition(input: SourceMappableLocation): SourceMappableLocation; - } - - export const identitySourceMapper = { getOriginalPosition: identity, getGeneratedPosition: identity }; - - export interface SourceMapDecodeHost { - readFile(path: string): string | undefined; - fileExists(path: string): boolean; - getCanonicalFileName(path: string): string; - log(text: string): void; - useCaseSensitiveFileNames: boolean; - } - - export function decode(host: SourceMapDecodeHost, mapPath: string, map: SourceMapData, program?: Program, fallbackCache = createSourceFileLikeCache(host)): SourceMapper { - const currentDirectory = getDirectoryPath(mapPath); - const sourceRoot = map.sourceRoot ? getNormalizedAbsolutePath(map.sourceRoot, currentDirectory) : currentDirectory; - let decodedMappings: ProcessedSourceMapPosition[]; - let generatedOrderedMappings: ProcessedSourceMapPosition[]; - let sourceOrderedMappings: ProcessedSourceMapPosition[]; - - return { - getOriginalPosition, - getGeneratedPosition - }; - - function getGeneratedPosition(loc: SourceMappableLocation): SourceMappableLocation { - const maps = getSourceOrderedMappings(); - if (!length(maps)) return loc; - let targetIndex = binarySearch(maps, { sourcePath: loc.fileName, sourcePosition: loc.position }, identity, compareProcessedPositionSourcePositions); - if (targetIndex < 0 && maps.length > 0) { - // if no exact match, closest is 2's compliment of result - targetIndex = ~targetIndex; - } - if (!maps[targetIndex] || comparePaths(loc.fileName, maps[targetIndex].sourcePath, sourceRoot, !host.useCaseSensitiveFileNames) !== 0) { - return loc; - } - return { fileName: getNormalizedAbsolutePath(map.file!, sourceRoot), position: maps[targetIndex].emittedPosition }; // Closest pos - } - - function getOriginalPosition(loc: SourceMappableLocation): SourceMappableLocation { - const maps = getGeneratedOrderedMappings(); - if (!length(maps)) return loc; - let targetIndex = binarySearch(maps, { emittedPosition: loc.position }, identity, compareProcessedPositionEmittedPositions); - if (targetIndex < 0 && maps.length > 0) { - // if no exact match, closest is 2's compliment of result - targetIndex = ~targetIndex; - } - return { fileName: getNormalizedAbsolutePath(maps[targetIndex].sourcePath, sourceRoot), position: maps[targetIndex].sourcePosition }; // Closest pos - } - - function getSourceFileLike(fileName: string, location: string): SourceFileLike | undefined { - // Lookup file in program, if provided - const path = toPath(fileName, location, host.getCanonicalFileName); - const file = program && program.getSourceFileByPath(path); - // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file - if (!file || file.resolvedPath !== path) { - // Otherwise check the cache (which may hit disk) - return fallbackCache.get(path); - } - return file; - } - - function getPositionOfLineAndCharacterUsingName(fileName: string, directory: string, line: number, character: number) { - const file = getSourceFileLike(fileName, directory); - if (!file) { - return -1; - } - return getPositionOfLineAndCharacter(file, line, character); - } - - function getDecodedMappings() { - return decodedMappings || (decodedMappings = calculateDecodedMappings(map, processPosition, host)); - } - - function getSourceOrderedMappings() { - return sourceOrderedMappings || (sourceOrderedMappings = getDecodedMappings().slice().sort(compareProcessedPositionSourcePositions)); - } - - function getGeneratedOrderedMappings() { - return generatedOrderedMappings || (generatedOrderedMappings = getDecodedMappings().slice().sort(compareProcessedPositionEmittedPositions)); - } - - function compareProcessedPositionSourcePositions(a: ProcessedSourceMapPosition, b: ProcessedSourceMapPosition) { - return comparePaths(a.sourcePath, b.sourcePath, sourceRoot, !host.useCaseSensitiveFileNames) || - compareValues(a.sourcePosition, b.sourcePosition); - } - - function compareProcessedPositionEmittedPositions(a: ProcessedSourceMapPosition, b: ProcessedSourceMapPosition) { - return compareValues(a.emittedPosition, b.emittedPosition); - } - - function processPosition(position: RawSourceMapPosition): ProcessedSourceMapPosition { - const sourcePath = map.sources[position.sourceIndex]; - return { - emittedPosition: getPositionOfLineAndCharacterUsingName(map.file!, currentDirectory, position.emittedLine, position.emittedColumn), - sourcePosition: getPositionOfLineAndCharacterUsingName(sourcePath, sourceRoot, position.sourceLine, position.sourceColumn), - sourcePath, - // TODO: Consider using `name` field to remap the expected identifier to scan for renames to handle another tool renaming oout output - // name: position.nameIndex ? map.names[position.nameIndex] : undefined - }; - } - } - - export interface MappingsDecoder extends Iterator { - readonly decodingIndex: number; - readonly error: string | undefined; - readonly lastSpan: SourceMapSpan; - } - - export function decodeMappings(map: SourceMapData): MappingsDecoder { - const state: DecoderState = { - encodedText: map.mappings, - currentNameIndex: undefined, - sourceMapNamesLength: map.names ? map.names.length : undefined, - currentEmittedColumn: 0, - currentEmittedLine: 0, - currentSourceColumn: 0, - currentSourceLine: 0, - currentSourceIndex: 0, - decodingIndex: 0 - }; - function captureSpan(): SourceMapSpan { - return { - emittedColumn: state.currentEmittedColumn, - emittedLine: state.currentEmittedLine, - sourceColumn: state.currentSourceColumn, - sourceIndex: state.currentSourceIndex, - sourceLine: state.currentSourceLine, - nameIndex: state.currentNameIndex - }; - } - return { - get decodingIndex() { return state.decodingIndex; }, - get error() { return state.error; }, - get lastSpan() { return captureSpan(); }, - next() { - if (hasCompletedDecoding(state) || state.error) return { done: true, value: undefined as never }; - if (!decodeSinglePosition(state)) return { done: true, value: undefined as never }; - return { done: false, value: captureSpan() }; - } - }; - } - - function calculateDecodedMappings(map: SourceMapData, processPosition: (position: RawSourceMapPosition) => T, host?: { log?(s: string): void }): T[] { - const decoder = decodeMappings(map); - const positions = arrayFrom(decoder, processPosition); - if (decoder.error) { - if (host && host.log) { - host.log(`Encountered error while decoding sourcemap: ${decoder.error}`); - } - return []; - } - return positions; - } - - interface ProcessedSourceMapPosition { - emittedPosition: number; - sourcePosition: number; - sourcePath: string; - } - - interface RawSourceMapPosition { - emittedLine: number; - emittedColumn: number; - sourceLine: number; - sourceColumn: number; - sourceIndex: number; - nameIndex?: number; - } - - interface DecoderState { - decodingIndex: number; - currentEmittedLine: number; - currentEmittedColumn: number; - currentSourceLine: number; - currentSourceColumn: number; - currentSourceIndex: number; - currentNameIndex: number | undefined; - encodedText: string; - sourceMapNamesLength?: number; - error?: string; - } - - function hasCompletedDecoding(state: DecoderState) { - return state.decodingIndex === state.encodedText.length; - } - - function decodeSinglePosition(state: DecoderState): boolean { - while (state.decodingIndex < state.encodedText.length) { - const char = state.encodedText.charCodeAt(state.decodingIndex); - if (char === CharacterCodes.semicolon) { - // New line - state.currentEmittedLine++; - state.currentEmittedColumn = 0; - state.decodingIndex++; - continue; - } - - if (char === CharacterCodes.comma) { - // Next entry is on same line - no action needed - state.decodingIndex++; - continue; - } - - // Read the current position - // 1. Column offset from prev read jsColumn - state.currentEmittedColumn += base64VLQFormatDecode(); - // Incorrect emittedColumn dont support this map - if (createErrorIfCondition(state.currentEmittedColumn < 0, "Invalid emittedColumn found")) { - return false; - } - // Dont support reading mappings that dont have information about original source and its line numbers - if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after emitted column")) { - return false; - } - - // 2. Relative sourceIndex - state.currentSourceIndex += base64VLQFormatDecode(); - // Incorrect sourceIndex dont support this map - if (createErrorIfCondition(state.currentSourceIndex < 0, "Invalid sourceIndex found")) { - return false; - } - // Dont support reading mappings that dont have information about original source position - if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after sourceIndex")) { - return false; - } - - // 3. Relative sourceLine 0 based - state.currentSourceLine += base64VLQFormatDecode(); - // Incorrect sourceLine dont support this map - if (createErrorIfCondition(state.currentSourceLine < 0, "Invalid sourceLine found")) { - return false; - } - // Dont support reading mappings that dont have information about original source and its line numbers - if (createErrorIfCondition(isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: No entries after emitted Line")) { - return false; - } - - // 4. Relative sourceColumn 0 based - state.currentSourceColumn += base64VLQFormatDecode(); - // Incorrect sourceColumn dont support this map - if (createErrorIfCondition(state.currentSourceColumn < 0, "Invalid sourceLine found")) { - return false; - } - // 5. Check if there is name: - if (!isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex)) { - if (state.currentNameIndex === undefined) { - state.currentNameIndex = 0; - } - state.currentNameIndex += base64VLQFormatDecode(); - // Incorrect nameIndex dont support this map - // TODO: If we start using `name`s, issue errors when they aren't correct in the sourcemap - // if (createErrorIfCondition(state.currentNameIndex < 0 || state.currentNameIndex >= state.sourceMapNamesLength, "Invalid name index for the source map entry")) { - // return; - // } - } - // Dont support reading mappings that dont have information about original source and its line numbers - if (createErrorIfCondition(!isSourceMappingSegmentEnd(state.encodedText, state.decodingIndex), "Unsupported Error Format: There are more entries after " + (state.currentNameIndex === undefined ? "sourceColumn" : "nameIndex"))) { - return false; - } - - // Entry should be complete - return true; - } - - createErrorIfCondition(/*condition*/ true, "No encoded entry found"); - return false; - - function createErrorIfCondition(condition: boolean, errormsg: string) { - if (state.error) { - // An error was already reported - return true; - } - - if (condition) { - state.error = errormsg; - } - - return condition; - } - - function base64VLQFormatDecode(): number { - let moreDigits = true; - let shiftCount = 0; - let value = 0; - - for (; moreDigits; state.decodingIndex++) { - if (createErrorIfCondition(state.decodingIndex >= state.encodedText.length, "Error in decoding base64VLQFormatDecode, past the mapping string")) { - return undefined!; // TODO: GH#18217 - } - - // 6 digit number - const currentByte = base64FormatDecode(state.encodedText.charAt(state.decodingIndex)); - - // If msb is set, we still have more bits to continue - moreDigits = (currentByte & 32) !== 0; - - // least significant 5 bits are the next msbs in the final value. - value = value | ((currentByte & 31) << shiftCount); - shiftCount += 5; - } - - // Least significant bit if 1 represents negative and rest of the msb is actual absolute value - if ((value & 1) === 0) { - // + number - value = value >> 1; - } - else { - // - number - value = value >> 1; - value = -value; - } - - return value; - } - } - - function base64FormatDecode(char: string) { - return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(char); - } - - function isSourceMappingSegmentEnd(encodedText: string, pos: number) { - return (pos === encodedText.length || - encodedText.charCodeAt(pos) === CharacterCodes.comma || - encodedText.charCodeAt(pos) === CharacterCodes.semicolon); - } -} diff --git a/src/compiler/transformer.ts b/src/compiler/transformer.ts index b78202e0c3..fdcaadb404 100644 --- a/src/compiler/transformer.ts +++ b/src/compiler/transformer.ts @@ -68,6 +68,14 @@ namespace ts { return transformers; } + export function noEmitSubstitution(_hint: EmitHint, node: Node) { + return node; + } + + export function noEmitNotification(hint: EmitHint, node: Node, callback: (hint: EmitHint, node: Node) => void) { + callback(hint, node); + } + /** * Transforms an array of SourceFiles by passing them through each transformer. * @@ -87,8 +95,8 @@ namespace ts { let lexicalEnvironmentStackOffset = 0; let lexicalEnvironmentSuspended = false; let emitHelpers: EmitHelper[] | undefined; - let onSubstituteNode: TransformationContext["onSubstituteNode"] = (_, node) => node; - let onEmitNode: TransformationContext["onEmitNode"] = (hint, node, callback) => callback(hint, node); + let onSubstituteNode: TransformationContext["onSubstituteNode"] = noEmitSubstitution; + let onEmitNode: TransformationContext["onEmitNode"] = noEmitNotification; let state = TransformationState.Uninitialized; const diagnostics: DiagnosticWithLocation[] = []; diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 4822f1dc06..c41f47f3c4 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -25,7 +25,7 @@ "checker.ts", "factory.ts", "visitor.ts", - "sourcemapDecoder.ts", + "sourcemap.ts", "transformers/utilities.ts", "transformers/destructuring.ts", "transformers/ts.ts", @@ -42,8 +42,6 @@ "transformers/declarations/diagnostics.ts", "transformers/declarations.ts", "transformer.ts", - "sourcemap.ts", - "comments.ts", "emitter.ts", "watchUtilities.ts", "program.ts", diff --git a/src/compiler/types.ts b/src/compiler/types.ts index 90accc20a1..45e6347592 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2963,16 +2963,10 @@ namespace ts { sourceIndex: number; } - export interface SourceMapData { - sourceMapFilePath: string; // Where the sourcemap file is written - jsSourceMappingURL: string; // source map URL written in the .js file - sourceMapFile: string; // Source map's file field - .js file name - sourceMapSourceRoot: string; // Source map's sourceRoot field - location where the sources will be present if not "" - sourceMapSources: string[]; // Source map's sources field - list of sources that can be indexed in this source map - sourceMapSourcesContent?: (string | null)[]; // Source map's sourcesContent field - list of the sources' text to be embedded in the source map - inputSourceFileNames: string[]; // Input source file (which one can use on program to get the file), 1:1 mapping with the sourceMapSources list - sourceMapNames?: string[]; // Source map's names field - list of names that can be indexed in this source map - sourceMapMappings: string; // Source map's mapping field - encoded source map spans + /* @internal */ + export interface SourceMapEmitResult { + inputSourceFileNames: ReadonlyArray; // Input source file (which one can use on program to get the file), 1:1 mapping with the sourceMap.sources list + sourceMap: RawSourceMap; } /** Return code used by getEmitOutput function to indicate status of the function */ @@ -2994,7 +2988,7 @@ namespace ts { /** Contains declaration emit diagnostics */ diagnostics: ReadonlyArray; emittedFiles?: string[]; // Array of files the compiler wrote to disk - /* @internal */ sourceMaps?: SourceMapData[]; // Array of sourceMapData if compiler emitted sourcemaps + /* @internal */ sourceMaps?: SourceMapEmitResult[]; // Array of sourceMapData if compiler emitted sourcemaps /* @internal */ exportedModulesFromDeclarationEmit?: ExportedModulesFromDeclarationEmit; } @@ -5217,6 +5211,7 @@ namespace ts { IdentifierName, // Emitting an IdentifierName MappedTypeParameter, // Emitting a TypeParameterDeclaration inside of a MappedTypeNode Unspecified, // Emitting an otherwise unspecified node + EmbeddedStatement, // Emitting an embedded statement } /* @internal */ @@ -5386,8 +5381,8 @@ namespace ts { printBundle(bundle: Bundle): string; /*@internal*/ writeNode(hint: EmitHint, node: Node, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void; /*@internal*/ writeList(format: ListFormat, list: NodeArray | undefined, sourceFile: SourceFile | undefined, writer: EmitTextWriter): void; - /*@internal*/ writeFile(sourceFile: SourceFile, writer: EmitTextWriter): void; - /*@internal*/ writeBundle(bundle: Bundle, writer: EmitTextWriter, info?: BundleInfo): void; + /*@internal*/ writeFile(sourceFile: SourceFile, writer: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined): void; + /*@internal*/ writeBundle(bundle: Bundle, info: BundleInfo | undefined, writer: EmitTextWriter, sourceMapGenerator: SourceMapGenerator | undefined): void; } /** @@ -5467,15 +5462,90 @@ namespace ts { /*@internal*/ target?: CompilerOptions["target"]; /*@internal*/ sourceMap?: boolean; /*@internal*/ inlineSourceMap?: boolean; + /*@internal*/ inlineSources?: boolean; /*@internal*/ extendedDiagnostics?: boolean; /*@internal*/ onlyPrintJsDocStyle?: boolean; /*@internal*/ neverAsciiEscape?: boolean; } + /* @internal */ + export interface RawSourceMap { + version: 3; + file: string; + sourceRoot?: string | null; + sources: string[]; + sourcesContent?: (string | null)[] | null; + mappings: string; + names?: string[] | null; + } + + /** + * Generates a source map. + */ + /* @internal */ + export interface SourceMapGenerator { + getSources(): ReadonlyArray; + /** + * Adds a source to the source map. + */ + addSource(fileName: string): number; + /** + * Set the content for a source. + */ + setSourceContent(sourceIndex: number, content: string | null): void; + /** + * Adds a name. + */ + addName(name: string): number; + /** + * Adds a mapping without source information. + */ + addMapping(generatedLine: number, generatedCharacter: number): void; + /** + * Adds a mapping with source information. + */ + addMapping(generatedLine: number, generatedCharacter: number, sourceIndex: number, sourceLine: number, sourceCharacter: number, nameIndex?: number): void; + /** + * Appends a source map. + */ + appendSourceMap(generatedLine: number, generatedCharacter: number, sourceMap: RawSourceMap, sourceMapPath: string): void; + /** + * Gets the source map as a `RawSourceMap` object. + */ + toJSON(): RawSourceMap; + /** + * Gets the string representation of the source map. + */ + toString(): string; + } + + /* @internal */ + export interface DocumentPositionMapperHost { + getSourceFileLike(path: Path): SourceFileLike | undefined; + getCanonicalFileName(path: string): string; + log?(text: string): void; + } + + /** + * Maps positions between source and generated files. + */ + /* @internal */ + export interface DocumentPositionMapper { + getSourcePosition(input: DocumentPosition): DocumentPosition; + getGeneratedPosition(input: DocumentPosition): DocumentPosition; + } + + /* @internal */ + export interface DocumentPosition { + fileName: string; + pos: number; + } + /* @internal */ export interface EmitTextWriter extends SymbolWriter { write(s: string): void; - writeTextOfNode(text: string, node: Node): void; + writeTrailingSemicolon(text: string): void; + writeComment(text: string): void; getText(): string; rawWrite(s: string): void; writeLiteral(s: string): void; diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 8813c705eb..0cc32767d7 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -64,7 +64,6 @@ namespace ts { getText: () => str, write: writeText, rawWrite: writeText, - writeTextOfNode: writeText, writeKeyword: writeText, writeOperator: writeText, writePunctuation: writeText, @@ -73,7 +72,9 @@ namespace ts { writeLiteral: writeText, writeParameter: writeText, writeProperty: writeText, - writeSymbol: writeText, + writeSymbol: (s, _) => writeText(s), + writeTrailingSemicolon: writeText, + writeComment: writeText, getTextPos: () => str.length, getLine: () => 0, getColumn: () => 0, @@ -3201,18 +3202,11 @@ namespace ts { } } - function writeTextOfNode(text: string, node: Node) { - const s = getTextOfNodeFromSourceText(text, node); - write(s); - updateLineCountAndPosFor(s); - } - reset(); return { write, rawWrite, - writeTextOfNode, writeLiteral, writeLine, increaseIndent: () => { indent++; }, @@ -3235,7 +3229,79 @@ namespace ts { writePunctuation: write, writeSpace: write, writeStringLiteral: write, - writeSymbol: write + writeSymbol: (s, _) => write(s), + writeTrailingSemicolon: write, + writeComment: write + }; + } + + export function getTrailingSemicolonOmittingWriter(writer: EmitTextWriter): EmitTextWriter { + let pendingTrailingSemicolon = false; + + function commitPendingTrailingSemicolon() { + if (pendingTrailingSemicolon) { + writer.writeTrailingSemicolon(";"); + pendingTrailingSemicolon = false; + } + } + + return { + ...writer, + writeTrailingSemicolon() { + pendingTrailingSemicolon = true; + }, + writeLiteral(s) { + commitPendingTrailingSemicolon(); + writer.writeLiteral(s); + }, + writeStringLiteral(s) { + commitPendingTrailingSemicolon(); + writer.writeStringLiteral(s); + }, + writeSymbol(s, sym) { + commitPendingTrailingSemicolon(); + writer.writeSymbol(s, sym); + }, + writePunctuation(s) { + commitPendingTrailingSemicolon(); + writer.writePunctuation(s); + }, + writeKeyword(s) { + commitPendingTrailingSemicolon(); + writer.writeKeyword(s); + }, + writeOperator(s) { + commitPendingTrailingSemicolon(); + writer.writeOperator(s); + }, + writeParameter(s) { + commitPendingTrailingSemicolon(); + writer.writeParameter(s); + }, + writeSpace(s) { + commitPendingTrailingSemicolon(); + writer.writeSpace(s); + }, + writeProperty(s) { + commitPendingTrailingSemicolon(); + writer.writeProperty(s); + }, + writeComment(s) { + commitPendingTrailingSemicolon(); + writer.writeComment(s); + }, + writeLine() { + commitPendingTrailingSemicolon(); + writer.writeLine(); + }, + increaseIndent() { + commitPendingTrailingSemicolon(); + writer.increaseIndent(); + }, + decreaseIndent() { + commitPendingTrailingSemicolon(); + writer.decreaseIndent(); + } }; } @@ -3515,13 +3581,13 @@ namespace ts { writeComment: (text: string, lineMap: ReadonlyArray, writer: EmitTextWriter, commentPos: number, commentEnd: number, newLine: string) => void) { if (comments && comments.length > 0) { if (leadingSeparator) { - writer.write(" "); + writer.writeSpace(" "); } let emitInterveningSeparator = false; for (const comment of comments) { if (emitInterveningSeparator) { - writer.write(" "); + writer.writeSpace(" "); emitInterveningSeparator = false; } @@ -3535,7 +3601,7 @@ namespace ts { } if (emitInterveningSeparator && trailingSeparator) { - writer.write(" "); + writer.writeSpace(" "); } } } @@ -3669,7 +3735,7 @@ namespace ts { } else { // Single line comment of style //.... - writer.write(text.substring(commentPos, commentEnd)); + writer.writeComment(text.substring(commentPos, commentEnd)); } } @@ -3678,14 +3744,14 @@ namespace ts { const currentLineText = text.substring(pos, end).replace(/^\s+|\s+$/g, ""); if (currentLineText) { // trimmed forward and ending spaces text - writer.write(currentLineText); + writer.writeComment(currentLineText); if (end !== commentEnd) { writer.writeLine(); } } else { // Empty string - make sure we write empty line - writer.writeLiteral(newLine); + writer.rawWrite(newLine); } } diff --git a/src/harness/harness.ts b/src/harness/harness.ts index d17eae4f80..fc27552925 100644 --- a/src/harness/harness.ts +++ b/src/harness/harness.ts @@ -1646,6 +1646,7 @@ namespace Harness { else { sourceMapCode = ""; result.maps.forEach(sourceMap => { + if (sourceMapCode) sourceMapCode += "\r\n"; sourceMapCode += fileOutput(sourceMap, harnessSettings); }); } @@ -1671,6 +1672,9 @@ namespace Harness { let jsCode = ""; result.js.forEach(file => { + if (jsCode.length && jsCode.charCodeAt(jsCode.length - 1) !== ts.CharacterCodes.lineFeed) { + jsCode += "\r\n"; + } jsCode += fileOutput(file, harnessSettings); }); diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index a1aa07f873..92c3e2d4a9 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -1,44 +1,36 @@ namespace Harness.SourceMapRecorder { interface SourceMapSpanWithDecodeErrors { - sourceMapSpan: ts.SourceMapSpan; + sourceMapSpan: ts.Mapping; decodeErrors: string[] | undefined; } namespace SourceMapDecoder { let sourceMapMappings: string; let decodingIndex: number; - let mappings: ts.sourcemaps.MappingsDecoder | undefined; + let mappings: ts.MappingsDecoder | undefined; export interface DecodedMapping { - sourceMapSpan: ts.SourceMapSpan; + sourceMapSpan: ts.Mapping; error?: string; } - export function initializeSourceMapDecoding(sourceMapData: ts.SourceMapData) { + export function initializeSourceMapDecoding(sourceMapData: ts.SourceMapEmitResult) { decodingIndex = 0; - sourceMapMappings = sourceMapData.sourceMapMappings; - mappings = ts.sourcemaps.decodeMappings({ - version: 3, - file: sourceMapData.sourceMapFile, - sources: sourceMapData.sourceMapSources, - sourceRoot: sourceMapData.sourceMapSourceRoot, - sourcesContent: sourceMapData.sourceMapSourcesContent, - mappings: sourceMapData.sourceMapMappings, - names: sourceMapData.sourceMapNames - }); + sourceMapMappings = sourceMapData.sourceMap.mappings; + mappings = ts.decodeMappings(sourceMapData.sourceMap.mappings); } export function decodeNextEncodedSourceMapSpan(): DecodedMapping { if (!mappings) return ts.Debug.fail("not initialized"); const result = mappings.next(); - if (result.done) return { error: mappings.error || "No encoded entry found", sourceMapSpan: mappings.lastSpan }; + if (result.done) return { error: mappings.error || "No encoded entry found", sourceMapSpan: mappings.state }; return { sourceMapSpan: result.value }; } export function hasCompletedDecoding() { if (!mappings) return ts.Debug.fail("not initialized"); - return mappings.decodingIndex === sourceMapMappings.length; + return mappings.pos === sourceMapMappings.length; } export function getRemainingDecodeString() { @@ -49,7 +41,7 @@ namespace Harness.SourceMapRecorder { namespace SourceMapSpanWriter { let sourceMapRecorder: Compiler.WriterAggregator; let sourceMapSources: string[]; - let sourceMapNames: string[] | undefined; + let sourceMapNames: string[] | null | undefined; let jsFile: documents.TextDocument; let jsLineMap: ReadonlyArray; @@ -61,10 +53,10 @@ namespace Harness.SourceMapRecorder { let nextJsLineToWrite: number; let spanMarkerContinues: boolean; - export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapData, currentJsFile: documents.TextDocument) { + export function initializeSourceMapSpanWriter(sourceMapRecordWriter: Compiler.WriterAggregator, sourceMapData: ts.SourceMapEmitResult, currentJsFile: documents.TextDocument) { sourceMapRecorder = sourceMapRecordWriter; - sourceMapSources = sourceMapData.sourceMapSources; - sourceMapNames = sourceMapData.sourceMapNames; + sourceMapSources = sourceMapData.sourceMap.sources; + sourceMapNames = sourceMapData.sourceMap.names; jsFile = currentJsFile; jsLineMap = jsFile.lineStarts; @@ -75,43 +67,39 @@ namespace Harness.SourceMapRecorder { spanMarkerContinues = false; SourceMapDecoder.initializeSourceMapDecoding(sourceMapData); - sourceMapRecorder.WriteLine("==================================================================="); - sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMapFile); - sourceMapRecorder.WriteLine("mapUrl: " + sourceMapData.jsSourceMappingURL); - sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMapSourceRoot); - sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMapSources); - if (sourceMapData.sourceMapSourcesContent) { - sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMapData.sourceMapSourcesContent)); + sourceMapRecorder.WriteLine("JsFile: " + sourceMapData.sourceMap.file); + sourceMapRecorder.WriteLine("mapUrl: " + ts.tryGetSourceMappingURL(jsFile.text, jsLineMap)); + sourceMapRecorder.WriteLine("sourceRoot: " + sourceMapData.sourceMap.sourceRoot); + sourceMapRecorder.WriteLine("sources: " + sourceMapData.sourceMap.sources); + if (sourceMapData.sourceMap.sourcesContent) { + sourceMapRecorder.WriteLine("sourcesContent: " + JSON.stringify(sourceMapData.sourceMap.sourcesContent)); } sourceMapRecorder.WriteLine("==================================================================="); } - function getSourceMapSpanString(mapEntry: ts.SourceMapSpan, getAbsentNameIndex?: boolean) { - let mapString = "Emitted(" + (mapEntry.emittedLine + 1) + ", " + (mapEntry.emittedColumn + 1) + ") Source(" + (mapEntry.sourceLine + 1) + ", " + (mapEntry.sourceColumn + 1) + ") + SourceIndex(" + mapEntry.sourceIndex + ")"; - if (mapEntry.nameIndex! >= 0 && mapEntry.nameIndex! < sourceMapNames!.length) { - mapString += " name (" + sourceMapNames![mapEntry.nameIndex!] + ")"; - } - else { - if ((mapEntry.nameIndex && mapEntry.nameIndex !== -1) || getAbsentNameIndex) { - mapString += " nameIndex (" + mapEntry.nameIndex + ")"; + function getSourceMapSpanString(mapEntry: ts.Mapping, getAbsentNameIndex?: boolean) { + let mapString = "Emitted(" + (mapEntry.generatedLine + 1) + ", " + (mapEntry.generatedCharacter + 1) + ")"; + if (ts.isSourceMapping(mapEntry)) { + mapString += " Source(" + (mapEntry.sourceLine + 1) + ", " + (mapEntry.sourceCharacter + 1) + ") + SourceIndex(" + mapEntry.sourceIndex + ")"; + if (mapEntry.nameIndex! >= 0 && mapEntry.nameIndex! < sourceMapNames!.length) { + mapString += " name (" + sourceMapNames![mapEntry.nameIndex!] + ")"; + } + else { + if ((mapEntry.nameIndex && mapEntry.nameIndex !== -1) || getAbsentNameIndex) { + mapString += " nameIndex (" + mapEntry.nameIndex + ")"; + } } } return mapString; } - export function recordSourceMapSpan(sourceMapSpan: ts.SourceMapSpan) { + export function recordSourceMapSpan(sourceMapSpan: ts.Mapping) { // verify the decoded span is same as the new span const decodeResult = SourceMapDecoder.decodeNextEncodedSourceMapSpan(); let decodeErrors: string[] | undefined; - if (typeof decodeResult.error === "string" - || decodeResult.sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine - || decodeResult.sourceMapSpan.emittedColumn !== sourceMapSpan.emittedColumn - || decodeResult.sourceMapSpan.sourceLine !== sourceMapSpan.sourceLine - || decodeResult.sourceMapSpan.sourceColumn !== sourceMapSpan.sourceColumn - || decodeResult.sourceMapSpan.sourceIndex !== sourceMapSpan.sourceIndex - || decodeResult.sourceMapSpan.nameIndex !== sourceMapSpan.nameIndex) { + if (typeof decodeResult.error === "string" || !ts.sameMapping(decodeResult.sourceMapSpan, sourceMapSpan)) { if (decodeResult.error) { decodeErrors = ["!!^^ !!^^ There was decoding error in the sourcemap at this location: " + decodeResult.error]; } @@ -121,7 +109,7 @@ namespace Harness.SourceMapRecorder { decodeErrors.push("!!^^ !!^^ Decoded span from sourcemap's mappings entry: " + getSourceMapSpanString(decodeResult.sourceMapSpan, /*getAbsentNameIndex*/ true) + " Span encoded by the emitter:" + getSourceMapSpanString(sourceMapSpan, /*getAbsentNameIndex*/ true)); } - if (spansOnSingleLine.length && spansOnSingleLine[0].sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine) { + if (spansOnSingleLine.length && spansOnSingleLine[0].sourceMapSpan.generatedLine !== sourceMapSpan.generatedLine) { // On different line from the one that we have been recording till now, writeRecordedSpans(); spansOnSingleLine = []; @@ -129,14 +117,21 @@ namespace Harness.SourceMapRecorder { spansOnSingleLine.push({ sourceMapSpan, decodeErrors }); } - export function recordNewSourceFileSpan(sourceMapSpan: ts.SourceMapSpan, newSourceFileCode: string) { - assert.isTrue(spansOnSingleLine.length === 0 || spansOnSingleLine[0].sourceMapSpan.emittedLine !== sourceMapSpan.emittedLine, "new file source map span should be on new line. We currently handle only that scenario"); + export function recordNewSourceFileSpan(sourceMapSpan: ts.Mapping, newSourceFileCode: string) { + let continuesLine = false; + if (spansOnSingleLine.length > 0 && spansOnSingleLine[0].sourceMapSpan.generatedCharacter === sourceMapSpan.generatedLine) { + writeRecordedSpans(); + spansOnSingleLine = []; + nextJsLineToWrite--; // walk back one line to reprint the line + continuesLine = true; + } + recordSourceMapSpan(sourceMapSpan); assert.isTrue(spansOnSingleLine.length === 1); sourceMapRecorder.WriteLine("-------------------------------------------------------------------"); - sourceMapRecorder.WriteLine("emittedFile:" + jsFile.file); - sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex]); + sourceMapRecorder.WriteLine("emittedFile:" + jsFile.file + (continuesLine ? ` (${sourceMapSpan.generatedLine + 1}, ${sourceMapSpan.generatedCharacter + 1})` : "")); + sourceMapRecorder.WriteLine("sourceFile:" + sourceMapSources[spansOnSingleLine[0].sourceMapSpan.sourceIndex!]); sourceMapRecorder.WriteLine("-------------------------------------------------------------------"); tsLineMap = ts.computeLineStarts(newSourceFileCode); @@ -195,7 +190,7 @@ namespace Harness.SourceMapRecorder { prevEmittedCol = 0; for (let i = 0; i < spansOnSingleLine.length; i++) { fn(spansOnSingleLine[i], i); - prevEmittedCol = spansOnSingleLine[i].sourceMapSpan.emittedColumn; + prevEmittedCol = spansOnSingleLine[i].sourceMapSpan.generatedCharacter; } } @@ -206,7 +201,7 @@ namespace Harness.SourceMapRecorder { } } - function writeSourceMapMarker(currentSpan: SourceMapSpanWithDecodeErrors, index: number, endColumn = currentSpan.sourceMapSpan.emittedColumn, endContinues = false) { + function writeSourceMapMarker(currentSpan: SourceMapSpanWithDecodeErrors, index: number, endColumn = currentSpan.sourceMapSpan.generatedCharacter, endContinues = false) { const markerId = getMarkerId(index); markerIds.push(markerId); @@ -223,7 +218,7 @@ namespace Harness.SourceMapRecorder { } function writeSourceMapSourceText(currentSpan: SourceMapSpanWithDecodeErrors, index: number) { - const sourcePos = tsLineMap[currentSpan.sourceMapSpan.sourceLine] + (currentSpan.sourceMapSpan.sourceColumn); + const sourcePos = tsLineMap[currentSpan.sourceMapSpan.sourceLine!] + (currentSpan.sourceMapSpan.sourceCharacter!); let sourceText = ""; if (prevWrittenSourcePos < sourcePos) { // Position that goes forward, get text @@ -255,7 +250,7 @@ namespace Harness.SourceMapRecorder { } if (spansOnSingleLine.length) { - const currentJsLine = spansOnSingleLine[0].sourceMapSpan.emittedLine; + const currentJsLine = spansOnSingleLine[0].sourceMapSpan.generatedLine; // Write js line writeJsFileLines(currentJsLine + 1); @@ -280,14 +275,14 @@ namespace Harness.SourceMapRecorder { } } - export function getSourceMapRecord(sourceMapDataList: ReadonlyArray, program: ts.Program, jsFiles: ReadonlyArray, declarationFiles: ReadonlyArray) { + export function getSourceMapRecord(sourceMapDataList: ReadonlyArray, program: ts.Program, jsFiles: ReadonlyArray, declarationFiles: ReadonlyArray) { const sourceMapRecorder = new Compiler.WriterAggregator(); for (let i = 0; i < sourceMapDataList.length; i++) { const sourceMapData = sourceMapDataList[i]; let prevSourceFile: ts.SourceFile | undefined; let currentFile: documents.TextDocument; - if (ts.endsWith(sourceMapData.sourceMapFile, ts.Extension.Dts)) { + if (ts.endsWith(sourceMapData.sourceMap.file, ts.Extension.Dts)) { if (sourceMapDataList.length > jsFiles.length) { currentFile = declarationFiles[Math.floor(i / 2)]; // When both kinds of source map are present, they alternate js/dts } @@ -305,11 +300,15 @@ namespace Harness.SourceMapRecorder { } SourceMapSpanWriter.initializeSourceMapSpanWriter(sourceMapRecorder, sourceMapData, currentFile); - const mapper = ts.sourcemaps.decodeMappings({ mappings: sourceMapData.sourceMapMappings, sources: sourceMapData.sourceMapSources }); + const mapper = ts.decodeMappings(sourceMapData.sourceMap.mappings); for (let { value: decodedSourceMapping, done } = mapper.next(); !done; { value: decodedSourceMapping, done } = mapper.next()) { - const currentSourceFile = program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex])!; + const currentSourceFile = ts.isSourceMapping(decodedSourceMapping) + ? program.getSourceFile(sourceMapData.inputSourceFileNames[decodedSourceMapping.sourceIndex]) + : undefined; if (currentSourceFile !== prevSourceFile) { - SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text); + if (currentSourceFile) { + SourceMapSpanWriter.recordNewSourceFileSpan(decodedSourceMapping, currentSourceFile.text); + } prevSourceFile = currentSourceFile; } else { diff --git a/src/server/editorServices.ts b/src/server/editorServices.ts index 6ce086a7fc..ac80f43c84 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2367,8 +2367,8 @@ namespace ts.server { } /*@internal*/ - getOriginalLocationEnsuringConfiguredProject(project: Project, location: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined { - const originalLocation = project.getSourceMapper().tryGetOriginalLocation(location); + getOriginalLocationEnsuringConfiguredProject(project: Project, location: DocumentPosition): DocumentPosition | undefined { + const originalLocation = project.getSourceMapper().tryGetSourcePosition(location); if (!originalLocation) return undefined; const { fileName } = originalLocation; diff --git a/src/server/session.ts b/src/server/session.ts index 43b624668d..7ff1364c4a 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -290,7 +290,7 @@ namespace ts.server { projects: Projects, defaultProject: Project, action: (project: Project) => ReadonlyArray, - getLocation: (t: T) => sourcemaps.SourceMappableLocation, + getLocation: (t: T) => DocumentPosition, resultsEqual: (a: T, b: T) => boolean, ): T[] { const outputs: T[] = []; @@ -312,18 +312,18 @@ namespace ts.server { function combineProjectOutputForRenameLocations( projects: Projects, defaultProject: Project, - initialLocation: sourcemaps.SourceMappableLocation, + initialLocation: DocumentPosition, findInStrings: boolean, findInComments: boolean ): ReadonlyArray { const outputs: RenameLocation[] = []; - combineProjectOutputWorker( + combineProjectOutputWorker( projects, defaultProject, initialLocation, ({ project, location }, tryAddToTodo) => { - for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.position, findInStrings, findInComments) || emptyArray) { + for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.pos, findInStrings, findInComments) || emptyArray) { if (!contains(outputs, output, documentSpansEqual) && !tryAddToTodo(project, documentSpanLocation(output))) { outputs.push(output); } @@ -335,29 +335,29 @@ namespace ts.server { return outputs; } - function getDefinitionLocation(defaultProject: Project, initialLocation: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined { - const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.position); + function getDefinitionLocation(defaultProject: Project, initialLocation: DocumentPosition): DocumentPosition | undefined { + const infos = defaultProject.getLanguageService().getDefinitionAtPosition(initialLocation.fileName, initialLocation.pos); const info = infos && firstOrUndefined(infos); - return info && { fileName: info.fileName, position: info.textSpan.start }; + return info && { fileName: info.fileName, pos: info.textSpan.start }; } function combineProjectOutputForReferences( projects: Projects, defaultProject: Project, - initialLocation: sourcemaps.SourceMappableLocation + initialLocation: DocumentPosition ): ReadonlyArray { const outputs: ReferencedSymbol[] = []; - combineProjectOutputWorker( + combineProjectOutputWorker( projects, defaultProject, initialLocation, ({ project, location }, getMappedLocation) => { - for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.position) || emptyArray) { + for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.pos) || emptyArray) { const mappedDefinitionFile = getMappedLocation(project, documentSpanLocation(outputReferencedSymbol.definition)); const definition: ReferencedSymbolDefinitionInfo = mappedDefinitionFile === undefined ? outputReferencedSymbol.definition : { ...outputReferencedSymbol.definition, - textSpan: createTextSpan(mappedDefinitionFile.position, outputReferencedSymbol.definition.textSpan.length), + textSpan: createTextSpan(mappedDefinitionFile.pos, outputReferencedSymbol.definition.textSpan.length), fileName: mappedDefinitionFile.fileName, }; let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, definition)); @@ -380,7 +380,7 @@ namespace ts.server { return outputs.filter(o => o.references.length !== 0); } - interface ProjectAndLocation { + interface ProjectAndLocation { readonly project: Project; readonly location: TLocation; } @@ -398,24 +398,24 @@ namespace ts.server { } } - type CombineProjectOutputCallback = ( + type CombineProjectOutputCallback = ( where: ProjectAndLocation, - getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => sourcemaps.SourceMappableLocation | undefined, + getMappedLocation: (project: Project, location: DocumentPosition) => DocumentPosition | undefined, ) => void; - function combineProjectOutputWorker( + function combineProjectOutputWorker( projects: Projects, defaultProject: Project, initialLocation: TLocation, cb: CombineProjectOutputCallback, - getDefinition: (() => sourcemaps.SourceMappableLocation | undefined) | undefined, + getDefinition: (() => DocumentPosition | undefined) | undefined, ): void { const projectService = defaultProject.projectService; let toDo: ProjectAndLocation[] | undefined; const seenProjects = createMap(); forEachProjectInProjects(projects, initialLocation && initialLocation.fileName, (project, path) => { - // TLocation shoud be either `sourcemaps.SourceMappableLocation` or `undefined`. Since `initialLocation` is `TLocation` this cast should be valid. - const location = (initialLocation ? { fileName: path, position: initialLocation.position } : undefined) as TLocation; + // TLocation shoud be either `DocumentPosition` or `undefined`. Since `initialLocation` is `TLocation` this cast should be valid. + const location = (initialLocation ? { fileName: path, pos: initialLocation.pos } : undefined) as TLocation; toDo = callbackProjectAndLocation({ project, location }, projectService, toDo, seenProjects, cb); }); @@ -436,13 +436,13 @@ namespace ts.server { } } - function getDefinitionInProject(definition: sourcemaps.SourceMappableLocation | undefined, definingProject: Project, project: Project): sourcemaps.SourceMappableLocation | undefined { + function getDefinitionInProject(definition: DocumentPosition | undefined, definingProject: Project, project: Project): DocumentPosition | undefined { if (!definition || project.containsFile(toNormalizedPath(definition.fileName))) return definition; - const mappedDefinition = definingProject.getLanguageService().getSourceMapper().tryGetGeneratedLocation(definition); + const mappedDefinition = definingProject.getLanguageService().getSourceMapper().tryGetGeneratedPosition(definition); return mappedDefinition && project.containsFile(toNormalizedPath(mappedDefinition.fileName)) ? mappedDefinition : undefined; } - function callbackProjectAndLocation( + function callbackProjectAndLocation( projectAndLocation: ProjectAndLocation, projectService: ProjectService, toDo: ProjectAndLocation[] | undefined, @@ -472,16 +472,16 @@ namespace ts.server { return toDo; } - function addToTodo(projectAndLocation: ProjectAndLocation, toDo: Push>, seenProjects: Map): void { + function addToTodo(projectAndLocation: ProjectAndLocation, toDo: Push>, seenProjects: Map): void { if (addToSeen(seenProjects, projectAndLocation.project.projectName)) toDo.push(projectAndLocation); } - function documentSpanLocation({ fileName, textSpan }: DocumentSpan): sourcemaps.SourceMappableLocation { - return { fileName, position: textSpan.start }; + function documentSpanLocation({ fileName, textSpan }: DocumentSpan): DocumentPosition { + return { fileName, pos: textSpan.start }; } - function getMappedLocation(location: sourcemaps.SourceMappableLocation, projectService: ProjectService, project: Project): sourcemaps.SourceMappableLocation | undefined { - const mapsTo = project.getSourceMapper().tryGetOriginalLocation(location); + function getMappedLocation(location: DocumentPosition, projectService: ProjectService, project: Project): DocumentPosition | undefined { + const mapsTo = project.getSourceMapper().tryGetSourcePosition(location); return mapsTo && projectService.fileExists(toNormalizedPath(mapsTo.fileName)) ? mapsTo : undefined; } @@ -941,7 +941,7 @@ namespace ts.server { kind: info.kind, name: info.name, textSpan: { - start: newLoc.position, + start: newLoc.pos, length: info.textSpan.length }, originalFileName: info.fileName, @@ -1038,7 +1038,7 @@ namespace ts.server { kind: info.kind, displayParts: info.displayParts, textSpan: { - start: newLoc.position, + start: newLoc.pos, length: info.textSpan.length }, originalFileName: info.fileName, @@ -1229,7 +1229,7 @@ namespace ts.server { const locations = combineProjectOutputForRenameLocations( projects, this.getDefaultProject(args), - { fileName: args.file, position }, + { fileName: args.file, pos: position }, !!args.findInStrings, !!args.findInComments ); @@ -1269,7 +1269,7 @@ namespace ts.server { const references = combineProjectOutputForReferences( projects, this.getDefaultProject(args), - { fileName: args.file, position }, + { fileName: args.file, pos: position }, ); if (simplifiedResult) { diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index 0f47a15b69..ae031da572 100644 --- a/src/services/getEditsForFileRename.ts +++ b/src/services/getEditsForFileRename.ts @@ -25,7 +25,7 @@ namespace ts { export function getPathUpdater(oldFileOrDirPath: string, newFileOrDirPath: string, getCanonicalFileName: GetCanonicalFileName, sourceMapper: SourceMapper | undefined): PathUpdater { const canonicalOldPath = getCanonicalFileName(oldFileOrDirPath); return path => { - const originalPath = sourceMapper && sourceMapper.tryGetOriginalLocation({ fileName: path, position: 0 }); + const originalPath = sourceMapper && sourceMapper.tryGetSourcePosition({ fileName: path, pos: 0 }); const updatedPath = getUpdatedPath(originalPath ? originalPath.fileName : path); return originalPath ? updatedPath === undefined ? undefined : makeCorrespondingRelativeChange(originalPath.fileName, updatedPath, path, getCanonicalFileName) diff --git a/src/services/sourcemaps.ts b/src/services/sourcemaps.ts index cf903bb1f2..92ebcc8c26 100644 --- a/src/services/sourcemaps.ts +++ b/src/services/sourcemaps.ts @@ -1,14 +1,11 @@ /* @internal */ namespace ts { - // Sometimes tools can sometimes see the following line as a source mapping url comment, so we mangle it a bit (the [M]) - const sourceMapCommentRegExp = /^\/\/[@#] source[M]appingURL=(.+)\s*$/; - const whitespaceOrMapCommentRegExp = /^\s*(\/\/[@#] .*)?$/; const base64UrlRegExp = /^data:(?:application\/json(?:;charset=[uU][tT][fF]-8);base64,([A-Za-z0-9+\/=]+)$)?/; export interface SourceMapper { toLineColumnOffset(fileName: string, position: number): LineAndCharacter; - tryGetOriginalLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined; - tryGetGeneratedLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined; + tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined; + tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined; clearCache(): void; } @@ -21,7 +18,7 @@ namespace ts { ): SourceMapper { const getCanonicalFileName = createGetCanonicalFileName(useCaseSensitiveFileNames); let sourcemappedFileCache: SourceFileLikeCache; - return { tryGetOriginalLocation, tryGetGeneratedLocation, toLineColumnOffset, clearCache }; + return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function toPath(fileName: string) { return ts.toPath(fileName, currentDirectory, getCanonicalFileName); @@ -32,44 +29,36 @@ namespace ts { if (!mappedFile) { return; } - const starts = getLineStarts(mappedFile); - for (let index = starts.length - 1; index >= 0; index--) { - const lineText = mappedFile.text.substring(starts[index], starts[index + 1]); - const comment = sourceMapCommentRegExp.exec(lineText); - if (comment) { - return comment[1]; - } - // If we see a non-whitespace/map comment-like line, break, to avoid scanning up the entire file - else if (!lineText.match(whitespaceOrMapCommentRegExp)) { - break; - } - } + + return tryGetSourceMappingURL(mappedFile.text, getLineStarts(mappedFile)); } - function convertDocumentToSourceMapper(file: { sourceMapper?: sourcemaps.SourceMapper }, contents: string, mapFileName: string) { - let maps: sourcemaps.SourceMapData | undefined; - try { - maps = JSON.parse(contents); - } - catch { - // swallow error - } - if (!maps || !maps.sources || !maps.file || !maps.mappings) { + function convertDocumentToSourceMapper(file: { sourceMapper?: DocumentPositionMapper }, contents: string, mapFileName: string) { + const map = tryParseRawSourceMap(contents); + if (!map || !map.sources || !map.file || !map.mappings) { // obviously invalid map - return file.sourceMapper = sourcemaps.identitySourceMapper; + return file.sourceMapper = identitySourceMapConsumer; } - return file.sourceMapper = sourcemaps.decode({ - readFile: s => host.readFile!(s), // TODO: GH#18217 - fileExists: s => host.fileExists!(s), // TODO: GH#18217 - useCaseSensitiveFileNames, + const program = getProgram(); + return file.sourceMapper = createDocumentPositionMapper({ + getSourceFileLike: s => { + // Lookup file in program, if provided + const file = program && program.getSourceFileByPath(s); + // file returned here could be .d.ts when asked for .ts file if projectReferences and module resolution created this source file + if (file === undefined || file.resolvedPath !== s) { + // Otherwise check the cache (which may hit disk) + return sourcemappedFileCache.get(s); + } + return file; + }, getCanonicalFileName, log, - }, mapFileName, maps, getProgram(), sourcemappedFileCache); + }, map, mapFileName); } - function getSourceMapper(fileName: string, file: SourceFileLike): sourcemaps.SourceMapper { + function getSourceMapper(fileName: string, file: SourceFileLike): DocumentPositionMapper { if (!host.readFile || !host.fileExists) { - return file.sourceMapper = sourcemaps.identitySourceMapper; + return file.sourceMapper = identitySourceMapConsumer; } if (file.sourceMapper) { return file.sourceMapper; @@ -97,19 +86,19 @@ namespace ts { return convertDocumentToSourceMapper(file, host.readFile(mapPath)!, mapPath); // TODO: GH#18217 } } - return file.sourceMapper = sourcemaps.identitySourceMapper; + return file.sourceMapper = identitySourceMapConsumer; } - function tryGetOriginalLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined { + function tryGetSourcePosition(info: DocumentPosition): DocumentPosition | undefined { if (!isDeclarationFileName(info.fileName)) return undefined; const file = getFile(info.fileName); if (!file) return undefined; - const newLoc = getSourceMapper(info.fileName, file).getOriginalPosition(info); - return newLoc === info ? undefined : tryGetOriginalLocation(newLoc) || newLoc; + const newLoc = getSourceMapper(info.fileName, file).getSourcePosition(info); + return newLoc === info ? undefined : tryGetSourcePosition(newLoc) || newLoc; } - function tryGetGeneratedLocation(info: sourcemaps.SourceMappableLocation): sourcemaps.SourceMappableLocation | undefined { + function tryGetGeneratedPosition(info: DocumentPosition): DocumentPosition | undefined { const program = getProgram(); const options = program.getCompilerOptions(); const outPath = options.outFile || options.out; @@ -141,4 +130,31 @@ namespace ts { sourcemappedFileCache = createSourceFileLikeCache(host); } } + + interface SourceFileLikeCache { + get(path: Path): SourceFileLike | undefined; + } + + function createSourceFileLikeCache(host: { readFile?: (path: string) => string | undefined, fileExists?: (path: string) => boolean }): SourceFileLikeCache { + const cached = createMap(); + return { + get(path: Path) { + if (cached.has(path)) { + return cached.get(path); + } + if (!host.fileExists || !host.readFile || !host.fileExists(path)) return; + // And failing that, check the disk + const text = host.readFile(path)!; // TODO: GH#18217 + const file = { + text, + lineMap: undefined, + getLineAndCharacterOfPosition(pos: number) { + return computeLineAndCharacterOfPosition(getLineStarts(this), pos); + } + } as SourceFileLike; + cached.set(path, file); + return file; + } + }; + } } diff --git a/src/services/textChanges.ts b/src/services/textChanges.ts index 47d8d0784e..2c6a5899fd 100644 --- a/src/services/textChanges.ts +++ b/src/services/textChanges.ts @@ -925,6 +925,9 @@ namespace ts.textChanges { this.writer.write(s); this.setLastNonTriviaPosition(s, /*force*/ false); } + writeComment(s: string): void { + this.writer.writeComment(s); + } writeKeyword(s: string): void { this.writer.writeKeyword(s); this.setLastNonTriviaPosition(s, /*force*/ false); @@ -937,6 +940,10 @@ namespace ts.textChanges { this.writer.writePunctuation(s); this.setLastNonTriviaPosition(s, /*force*/ false); } + writeTrailingSemicolon(s: string): void { + this.writer.writeTrailingSemicolon(s); + this.setLastNonTriviaPosition(s, /*force*/ false); + } writeParameter(s: string): void { this.writer.writeParameter(s); this.setLastNonTriviaPosition(s, /*force*/ false); @@ -957,9 +964,6 @@ namespace ts.textChanges { this.writer.writeSymbol(s, sym); this.setLastNonTriviaPosition(s, /*force*/ false); } - writeTextOfNode(text: string, node: Node): void { - this.writer.writeTextOfNode(text, node); - } writeLine(): void { this.writer.writeLine(); } diff --git a/src/services/types.ts b/src/services/types.ts index 73e41309a6..0d14ad2b97 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -86,12 +86,12 @@ namespace ts { getPositionOfLineAndCharacter(line: number, character: number): number; update(newText: string, textChangeRange: TextChangeRange): SourceFile; - /* @internal */ sourceMapper?: sourcemaps.SourceMapper; + /* @internal */ sourceMapper?: DocumentPositionMapper; } export interface SourceFileLike { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; - /*@internal*/ sourceMapper?: sourcemaps.SourceMapper; + /*@internal*/ sourceMapper?: DocumentPositionMapper; } export interface SourceMapSource { diff --git a/src/services/utilities.ts b/src/services/utilities.ts index 91b18a015c..e95e144d41 100644 --- a/src/services/utilities.ts +++ b/src/services/utilities.ts @@ -1459,6 +1459,7 @@ namespace ts { writeKeyword: text => writeKind(text, SymbolDisplayPartKind.keyword), writeOperator: text => writeKind(text, SymbolDisplayPartKind.operator), writePunctuation: text => writeKind(text, SymbolDisplayPartKind.punctuation), + writeTrailingSemicolon: text => writeKind(text, SymbolDisplayPartKind.punctuation), writeSpace: text => writeKind(text, SymbolDisplayPartKind.space), writeStringLiteral: text => writeKind(text, SymbolDisplayPartKind.stringLiteral), writeParameter: text => writeKind(text, SymbolDisplayPartKind.parameterName), @@ -1467,7 +1468,7 @@ namespace ts { writeSymbol, writeLine, write: unknownWrite, - writeTextOfNode: unknownWrite, + writeComment: unknownWrite, getText: () => "", getTextPos: () => 0, getColumn: () => 0, diff --git a/src/testRunner/projectsRunner.ts b/src/testRunner/projectsRunner.ts index 2d15133f39..457201c138 100644 --- a/src/testRunner/projectsRunner.ts +++ b/src/testRunner/projectsRunner.ts @@ -23,7 +23,7 @@ namespace project { program?: ts.Program; compilerOptions?: ts.CompilerOptions; errors: ReadonlyArray; - sourceMapData?: ReadonlyArray; + sourceMapData?: ReadonlyArray; } interface BatchCompileProjectTestCaseResult extends CompileProjectFilesResult { @@ -315,11 +315,11 @@ namespace project { // Clean up source map data that will be used in baselining if (sourceMapData) { for (const data of sourceMapData) { - for (let j = 0; j < data.sourceMapSources.length; j++) { - data.sourceMapSources[j] = this.cleanProjectUrl(data.sourceMapSources[j]); - } - data.jsSourceMappingURL = this.cleanProjectUrl(data.jsSourceMappingURL); - data.sourceMapSourceRoot = this.cleanProjectUrl(data.sourceMapSourceRoot); + data.sourceMap = { + ...data.sourceMap, + sources: data.sourceMap.sources.map(source => this.cleanProjectUrl(source)), + sourceRoot: data.sourceMap.sourceRoot && this.cleanProjectUrl(data.sourceMap.sourceRoot) + }; } } @@ -425,6 +425,7 @@ namespace project { skipDefaultLibCheck: false, moduleResolution: ts.ModuleResolutionKind.Classic, module: moduleKind, + newLine: ts.NewLineKind.CarriageReturnLineFeed, mapRoot: testCase.resolveMapRoot && testCase.mapRoot ? vpath.resolve(vfs.srcFolder, testCase.mapRoot) : testCase.mapRoot, diff --git a/src/testRunner/unittests/customTransforms.ts b/src/testRunner/unittests/customTransforms.ts index 819b5d80df..c4ff21d0a2 100644 --- a/src/testRunner/unittests/customTransforms.ts +++ b/src/testRunner/unittests/customTransforms.ts @@ -18,7 +18,7 @@ namespace ts { writeFile: (fileName, text) => outputs.set(fileName, text), }; - const program = createProgram(arrayFrom(fileMap.keys()), options, host); + const program = createProgram(arrayFrom(fileMap.keys()), { newLine: NewLineKind.LineFeed, ...options }, host); program.emit(/*targetSourceFile*/ undefined, host.writeFile, /*cancellationToken*/ undefined, /*emitOnlyDtsFiles*/ false, customTransformers); let content = ""; for (const [file, text] of arrayFrom(outputs.entries())) { diff --git a/src/testRunner/unittests/tsserverProjectSystem.ts b/src/testRunner/unittests/tsserverProjectSystem.ts index a113ea14d7..085b5cf18a 100644 --- a/src/testRunner/unittests/tsserverProjectSystem.ts +++ b/src/testRunner/unittests/tsserverProjectSystem.ts @@ -10058,7 +10058,7 @@ declare class TestLib { const configContent = JSON.stringify({ compilerOptions }); const aTsconfig: File = { path: "/a/tsconfig.json", content: configContent }; - const aDtsMapContent: SourceMapSection = { + const aDtsMapContent: RawSourceMap = { version: 3, file: "a.d.ts", sourceRoot: "", @@ -10082,7 +10082,7 @@ declare class TestLib { }; const bTsconfig: File = { path: "/b/tsconfig.json", content: configContent }; - const bDtsMapContent: SourceMapSection = { + const bDtsMapContent: RawSourceMap = { version: 3, file: "b.d.ts", sourceRoot: "", diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 37bcd66c81..c024d92620 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1849,17 +1849,6 @@ declare namespace ts { /** .ts file (index into sources array) associated with this span */ sourceIndex: number; } - interface SourceMapData { - sourceMapFilePath: string; - jsSourceMappingURL: string; - sourceMapFile: string; - sourceMapSourceRoot: string; - sourceMapSources: string[]; - sourceMapSourcesContent?: (string | null)[]; - inputSourceFileNames: string[]; - sourceMapNames?: string[]; - sourceMapMappings: string; - } /** Return code used by getEmitOutput function to indicate status of the function */ enum ExitStatus { Success = 0, @@ -2764,7 +2753,8 @@ declare namespace ts { Expression = 1, IdentifierName = 2, MappedTypeParameter = 3, - Unspecified = 4 + Unspecified = 4, + EmbeddedStatement = 5 } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d2850d0783..335283ce43 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1849,17 +1849,6 @@ declare namespace ts { /** .ts file (index into sources array) associated with this span */ sourceIndex: number; } - interface SourceMapData { - sourceMapFilePath: string; - jsSourceMappingURL: string; - sourceMapFile: string; - sourceMapSourceRoot: string; - sourceMapSources: string[]; - sourceMapSourcesContent?: (string | null)[]; - inputSourceFileNames: string[]; - sourceMapNames?: string[]; - sourceMapMappings: string; - } /** Return code used by getEmitOutput function to indicate status of the function */ enum ExitStatus { Success = 0, @@ -2764,7 +2753,8 @@ declare namespace ts { Expression = 1, IdentifierName = 2, MappedTypeParameter = 3, - Unspecified = 4 + Unspecified = 4, + EmbeddedStatement = 5 } interface TransformationContext { /** Gets the compiler options supplied to the transformer. */ diff --git a/tests/baselines/reference/declarationMapsMultifile.js.map b/tests/baselines/reference/declarationMapsMultifile.js.map index cf1f1472a6..80d8ac624c 100644 --- a/tests/baselines/reference/declarationMapsMultifile.js.map +++ b/tests/baselines/reference/declarationMapsMultifile.js.map @@ -1,3 +1,4 @@ //// [a.d.ts.map] -{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["a.ts"],"names":[],"mappings":"AAAA,qBAAa,GAAG;IACZ,OAAO,CAAC,CAAC,EAAE;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC;;;IAGtB,MAAM,CAAC,IAAI;CAGd"}//// [index.d.ts.map] +{"version":3,"file":"a.d.ts","sourceRoot":"","sources":["a.ts"],"names":[],"mappings":"AAAA,qBAAa,GAAG;IACZ,OAAO,CAAC,CAAC,EAAE;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC;;;IAGtB,MAAM,CAAC,IAAI;CAGd"} +//// [index.d.ts.map] {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAC,GAAG,EAAC,MAAM,KAAK,CAAC;AAExB,QAAA,MAAM,CAAC,KAAY,CAAC;AAGpB,eAAO,IAAI,CAAC;;CAAqB,CAAC;AAClC,OAAO,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/declarationMapsWithSourceMap.js.map b/tests/baselines/reference/declarationMapsWithSourceMap.js.map index 41114d427d..9a44237196 100644 --- a/tests/baselines/reference/declarationMapsWithSourceMap.js.map +++ b/tests/baselines/reference/declarationMapsWithSourceMap.js.map @@ -1,3 +1,4 @@ //// [bundle.js.map] -{"version":3,"file":"bundle.js","sourceRoot":"","sources":["tests/cases/compiler/a.ts","tests/cases/compiler/index.ts"],"names":[],"mappings":"AAAA;IAAA;IAOA,CAAC;IANG,qBAAO,GAAP,UAAQ,CAAc;QAClB,OAAO,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAC,CAAC;IACpB,CAAC;IACM,QAAI,GAAX;QACI,OAAO,IAAI,GAAG,EAAE,CAAC;IACrB,CAAC;IACL,UAAC;AAAD,CAAC,AAPD,IAOC;ACPD,IAAM,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;AACpB,CAAC,CAAC,OAAO,CAAC,EAAC,CAAC,EAAE,EAAE,EAAC,CAAC,CAAC;AAEnB,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAC,CAAC,EAAE,EAAE,EAAC,CAAC,CAAC"}//// [bundle.d.ts.map] +{"version":3,"file":"bundle.js","sourceRoot":"","sources":["tests/cases/compiler/a.ts","tests/cases/compiler/index.ts"],"names":[],"mappings":"AAAA;IAAA;IAOA,CAAC;IANG,qBAAO,GAAP,UAAQ,CAAc;QAClB,OAAO,EAAC,CAAC,EAAE,CAAC,CAAC,CAAC,EAAC,CAAC;IACpB,CAAC;IACM,QAAI,GAAX;QACI,OAAO,IAAI,GAAG,EAAE,CAAC;IACrB,CAAC;IACL,UAAC;AAAD,CAAC,AAPD,IAOC;ACPD,IAAM,CAAC,GAAG,IAAI,GAAG,EAAE,CAAC;AACpB,CAAC,CAAC,OAAO,CAAC,EAAC,CAAC,EAAE,EAAE,EAAC,CAAC,CAAC;AAEnB,IAAI,CAAC,GAAG,CAAC,CAAC,OAAO,CAAC,EAAC,CAAC,EAAE,EAAE,EAAC,CAAC,CAAC"} +//// [bundle.d.ts.map] {"version":3,"file":"bundle.d.ts","sourceRoot":"","sources":["tests/cases/compiler/a.ts","tests/cases/compiler/index.ts"],"names":[],"mappings":"AAAA,cAAM,GAAG;IACL,OAAO,CAAC,GAAG;QAAC,CAAC,EAAE,MAAM,CAAA;KAAC;;;IAGtB,MAAM,CAAC,IAAI;CAGd;ACPD,QAAA,MAAM,CAAC,KAAY,CAAC;AAGpB,QAAA,IAAI,CAAC;;CAAqB,CAAC"} \ No newline at end of file diff --git a/tests/baselines/reference/jsxFactoryIdentifier.js b/tests/baselines/reference/jsxFactoryIdentifier.js index 817ef769d5..a5ed7e6603 100644 --- a/tests/baselines/reference/jsxFactoryIdentifier.js +++ b/tests/baselines/reference/jsxFactoryIdentifier.js @@ -68,7 +68,8 @@ exports.createElement = Element.createElement; function toCamelCase(text) { return text[0].toLowerCase() + text.substring(1); } -//# sourceMappingURL=Element.js.map//// [test.js] +//# sourceMappingURL=Element.js.map +//// [test.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Element_1 = require("./Element"); diff --git a/tests/baselines/reference/jsxFactoryIdentifier.js.map b/tests/baselines/reference/jsxFactoryIdentifier.js.map index ea0db5040e..5ecfbe9d87 100644 --- a/tests/baselines/reference/jsxFactoryIdentifier.js.map +++ b/tests/baselines/reference/jsxFactoryIdentifier.js.map @@ -1,3 +1,4 @@ //// [Element.js.map] -{"version":3,"file":"Element.js","sourceRoot":"","sources":["Element.ts"],"names":[],"mappings":";;AAYA,IAAiB,OAAO,CAUvB;AAVD,WAAiB,OAAO;IACpB,SAAgB,SAAS,CAAC,EAAO;QAC7B,OAAO,EAAE,CAAC,wBAAwB,KAAK,SAAS,CAAC;IACrD,CAAC;IAFe,iBAAS,YAExB,CAAA;IAED,SAAgB,aAAa,CAAC,IAAW;QAErC,OAAO,EACN,CAAA;IACL,CAAC;IAJe,qBAAa,gBAI5B,CAAA;AACL,CAAC,EAVgB,OAAO,GAAP,eAAO,KAAP,eAAO,QAUvB;AAEU,QAAA,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAEjD,SAAS,WAAW,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"}//// [test.js.map] +{"version":3,"file":"Element.js","sourceRoot":"","sources":["Element.ts"],"names":[],"mappings":";;AAYA,IAAiB,OAAO,CAUvB;AAVD,WAAiB,OAAO;IACpB,SAAgB,SAAS,CAAC,EAAO;QAC7B,OAAO,EAAE,CAAC,wBAAwB,KAAK,SAAS,CAAC;IACrD,CAAC;IAFe,iBAAS,YAExB,CAAA;IAED,SAAgB,aAAa,CAAC,IAAW;QAErC,OAAO,EACN,CAAA;IACL,CAAC;IAJe,qBAAa,gBAI5B,CAAA;AACL,CAAC,EAVgB,OAAO,GAAP,eAAO,KAAP,eAAO,QAUvB;AAEU,QAAA,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAEjD,SAAS,WAAW,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"} +//// [test.js.map] {"version":3,"file":"test.js","sourceRoot":"","sources":["test.tsx"],"names":[],"mappings":";;AAAA,uCAAmC;AACnC,IAAI,aAAa,GAAG,iBAAO,CAAC,aAAa,CAAC;AAC1C,IAAI,CAIH,CAAC;AAEF,MAAM,CAAC;IACN,IAAI;QACH,OAAO;YACN,wBAAM,OAAO,EAAC,YAAY,GAAQ;YAClC,wBAAM,OAAO,EAAE,CAAC,CAAC,CAAE,CAAC,CAAC,GAAS;SAC9B,CAAC;IACH,CAAC;CACD"} \ No newline at end of file diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.js b/tests/baselines/reference/jsxFactoryQualifiedName.js index 63b45e878d..14906d7a08 100644 --- a/tests/baselines/reference/jsxFactoryQualifiedName.js +++ b/tests/baselines/reference/jsxFactoryQualifiedName.js @@ -67,7 +67,8 @@ exports.createElement = Element.createElement; function toCamelCase(text) { return text[0].toLowerCase() + text.substring(1); } -//# sourceMappingURL=Element.js.map//// [test.js] +//# sourceMappingURL=Element.js.map +//// [test.js] "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); const Element_1 = require("./Element"); diff --git a/tests/baselines/reference/jsxFactoryQualifiedName.js.map b/tests/baselines/reference/jsxFactoryQualifiedName.js.map index 4767276bd1..619537c50e 100644 --- a/tests/baselines/reference/jsxFactoryQualifiedName.js.map +++ b/tests/baselines/reference/jsxFactoryQualifiedName.js.map @@ -1,3 +1,4 @@ //// [Element.js.map] -{"version":3,"file":"Element.js","sourceRoot":"","sources":["Element.ts"],"names":[],"mappings":";;AAYA,IAAiB,OAAO,CAUvB;AAVD,WAAiB,OAAO;IACpB,SAAgB,SAAS,CAAC,EAAO;QAC7B,OAAO,EAAE,CAAC,wBAAwB,KAAK,SAAS,CAAC;IACrD,CAAC;IAFe,iBAAS,YAExB,CAAA;IAED,SAAgB,aAAa,CAAC,IAAW;QAErC,OAAO,EACN,CAAA;IACL,CAAC;IAJe,qBAAa,gBAI5B,CAAA;AACL,CAAC,EAVgB,OAAO,GAAP,eAAO,KAAP,eAAO,QAUvB;AAEU,QAAA,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAEjD,SAAS,WAAW,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"}//// [test.js.map] +{"version":3,"file":"Element.js","sourceRoot":"","sources":["Element.ts"],"names":[],"mappings":";;AAYA,IAAiB,OAAO,CAUvB;AAVD,WAAiB,OAAO;IACpB,SAAgB,SAAS,CAAC,EAAO;QAC7B,OAAO,EAAE,CAAC,wBAAwB,KAAK,SAAS,CAAC;IACrD,CAAC;IAFe,iBAAS,YAExB,CAAA;IAED,SAAgB,aAAa,CAAC,IAAW;QAErC,OAAO,EACN,CAAA;IACL,CAAC;IAJe,qBAAa,gBAI5B,CAAA;AACL,CAAC,EAVgB,OAAO,GAAP,eAAO,KAAP,eAAO,QAUvB;AAEU,QAAA,aAAa,GAAG,OAAO,CAAC,aAAa,CAAC;AAEjD,SAAS,WAAW,CAAC,IAAY;IAC7B,OAAO,IAAI,CAAC,CAAC,CAAC,CAAC,WAAW,EAAE,GAAG,IAAI,CAAC,SAAS,CAAC,CAAC,CAAC,CAAC;AACrD,CAAC"} +//// [test.js.map] {"version":3,"file":"test.js","sourceRoot":"","sources":["test.tsx"],"names":[],"mappings":";;AAAA,uCAAmC;AAEnC,IAAI,CAIH,CAAC;AAEF,MAAM,CAAC;IACN,IAAI;QACH,OAAO;YACN,0CAAM,OAAO,EAAC,YAAY,GAAQ;YAClC,0CAAM,OAAO,EAAE,CAAC,CAAC,CAAE,CAAC,CAAC,GAAS;SAC9B,CAAC;IACH,CAAC;CACD"} \ No newline at end of file diff --git a/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js b/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js index 7b7abae996..490f38acf4 100644 --- a/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js +++ b/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js @@ -18,7 +18,8 @@ var c = /** @class */ (function () { } return c; }()); -//# sourceMappingURL=app.js.map//// [app2.js] +//# sourceMappingURL=app.js.map +//// [app2.js] var d = /** @class */ (function () { function d() { } diff --git a/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js.map b/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js.map index 7a172c4e0a..b517b5694b 100644 --- a/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js.map +++ b/tests/baselines/reference/sourceMapWithCaseSensitiveFileNamesAndOutDir.js.map @@ -1,3 +1,4 @@ //// [app.js.map] -{"version":3,"file":"app.js","sourceRoot":"","sources":["../testFiles/app.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,wIAAwI;AACxI;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"}//// [app2.js.map] +{"version":3,"file":"app.js","sourceRoot":"","sources":["../testFiles/app.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,wIAAwI;AACxI;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"} +//// [app2.js.map] {"version":3,"file":"app2.js","sourceRoot":"","sources":["../testFiles/app2.ts"],"names":[],"mappings":"AAAA;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"} \ No newline at end of file diff --git a/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js b/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js index 3487a70dc7..6e49d774aa 100644 --- a/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js +++ b/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js @@ -18,7 +18,8 @@ var c = /** @class */ (function () { } return c; }()); -//# sourceMappingURL=app.js.map//// [app2.js] +//# sourceMappingURL=app.js.map +//// [app2.js] var d = /** @class */ (function () { function d() { } diff --git a/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js.map b/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js.map index 5025ed2212..ef9951d251 100644 --- a/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js.map +++ b/tests/baselines/reference/sourceMapWithNonCaseSensitiveFileNamesAndOutDir.js.map @@ -1,3 +1,4 @@ //// [app.js.map] -{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,0GAA0G;AAC1G;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"}//// [app2.js.map] +{"version":3,"file":"app.js","sourceRoot":"","sources":["app.ts"],"names":[],"mappings":"AAAA,gFAAgF;AAChF,0GAA0G;AAC1G;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"} +//// [app2.js.map] {"version":3,"file":"app2.js","sourceRoot":"","sources":["app2.ts"],"names":[],"mappings":"AAAA;IAAA;IACA,CAAC;IAAD,QAAC;AAAD,CAAC,AADD,IACC"} \ No newline at end of file