From b006287cb13b9d5719c824c123e08419c65e2669 Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Tue, 4 Sep 2018 17:50:06 -0700 Subject: [PATCH] Refactor and clean up sourcemap decoder --- scripts/build/gulp-typescript-oop/index.js | 6 +- src/compiler/builder.ts | 2 +- src/compiler/comments.ts | 431 --------- src/compiler/core.ts | 87 +- src/compiler/emitter.ts | 787 +++++++++++++++-- src/compiler/sourcemap.ts | 836 +++++++++--------- src/compiler/sourcemapDecoder.ts | 367 -------- src/compiler/tsconfig.json | 4 +- src/compiler/types.ts | 58 +- src/harness/sourceMapRecorder.ts | 102 +-- src/server/editorServices.ts | 4 +- src/server/session.ts | 62 +- src/services/getEditsForFileRename.ts | 2 +- src/services/sourcemaps.ts | 85 +- src/services/types.ts | 4 +- src/testRunner/projectsRunner.ts | 12 +- .../reference/api/tsserverlibrary.d.ts | 11 - tests/baselines/reference/api/typescript.d.ts | 11 - 18 files changed, 1385 insertions(+), 1486 deletions(-) delete mode 100644 src/compiler/comments.ts delete mode 100644 src/compiler/sourcemapDecoder.ts diff --git a/scripts/build/gulp-typescript-oop/index.js b/scripts/build/gulp-typescript-oop/index.js index 3ef5effaf5a..ed7a7d64a03 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 f4a61267144..f0cfde01272 100644 --- a/src/compiler/builder.ts +++ b/src/compiler/builder.ts @@ -410,7 +410,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/comments.ts b/src/compiler/comments.ts deleted file mode 100644 index 92423ac785c..00000000000 --- 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.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 emitNodeWithNestedComments(hint: EmitHint, node: Node, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { - if (emitFlags & EmitFlags.NoNestedComments) { - disabled = true; - emitCallback(hint, node); - disabled = false; - } - else { - emitCallback(hint, node); - } - } - - function emitBodyWithDetachedComments(node: Node, detachedRange: TextRange, emitCallback: (node: Node) => void) { - if (extendedDiagnostics) { - performance.mark("preEmitBodyWithDetachedComments"); - } - - const { pos, end } = detachedRange; - const emitFlags = getEmitFlags(node); - const skipLeadingComments = pos < 0 || (emitFlags & EmitFlags.NoLeadingComments) !== 0; - const skipTrailingComments = disabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; - - if (!skipLeadingComments) { - emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "preEmitBodyWithDetachedComments"); - } - - if (emitFlags & EmitFlags.NoNestedComments && !disabled) { - disabled = true; - emitCallback(node); - disabled = false; - } - else { - emitCallback(node); - } - - if (extendedDiagnostics) { - performance.mark("beginEmitBodyWithDetachedCommetns"); - } - - if (!skipTrailingComments) { - emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); - if (hasWrittenComment && !writer.isAtStartOfLine()) { - writer.writeLine(); - } - } - - if (extendedDiagnostics) { - performance.measure("commentTime", "beginEmitBodyWithDetachedCommetns"); - } - } - - function emitLeadingComments(pos: number, isEmittedNode: boolean) { - hasWrittenComment = false; - - if (isEmittedNode) { - forEachLeadingCommentToEmit(pos, emitLeadingComment); - } - else if (pos === 0) { - // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, - // unless it is a triple slash comment at the top of the file. - // For Example: - // /// - // declare var x; - // /// - // interface F {} - // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted - forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); - } - } - - function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { - if (isTripleSlashComment(commentPos, commentEnd)) { - emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); - } - } - - function 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.writeSpace(" "); - } - } - - 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.writeSpace(" "); - } - - 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.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 (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 1eaab5b1b59..2aaff6086da 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -1140,13 +1140,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 some(array) ? binarySearchKey(array, keySelector(value), keySelector, keyComparer, offset) : -1; + } + + /** + * 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]); @@ -2130,4 +2143,74 @@ namespace ts { deleted(oldItems[oldIndex++]); } } + + /** + * A list of sorted and unique values. Optimized for best performance when items are added + * in the sort order. + */ + export class SortedUniqueList implements Push { + private relationalComparer: (a: T, b: T) => Comparison; + private equalityComparer: (a: T, b: T) => boolean; + private sortedAndUnique = true; + private copyOnWrite = false; + private unsafeArray: T[] = []; + private unsafeLast: T | undefined = undefined; + + constructor(relationalComparer: Comparer, equalityComparer: EqualityComparer) { + this.relationalComparer = relationalComparer; + this.equalityComparer = equalityComparer; + } + + get size() { + return this.unsafeArray.length; + } + + get last() { + this.ensureSortedAndUnique(); + return this.unsafeLast === undefined + ? this.unsafeLast = lastOrUndefined(this.unsafeArray) + : this.unsafeLast; + } + + push(...values: T[]) { + for (const value of values) { + if (this.sortedAndUnique) { + const last = this.last; + if (last === undefined || this.relationalComparer(value, last) > 0) { + this.unsafeAdd(value, /*sortedAndUnique*/ true); + continue; + } + if (this.equalityComparer(value, last)) { + continue; + } + } + this.unsafeAdd(value, /*sortedAndUnique*/ false); + } + } + + toArray(): ReadonlyArray { + this.ensureSortedAndUnique(); + this.copyOnWrite = true; + return this.unsafeArray; + } + + private unsafeAdd(value: T, sortedAndUnique: boolean) { + if (this.copyOnWrite) { + this.unsafeArray = this.unsafeArray.slice(); + this.copyOnWrite = false; + } + + this.unsafeArray.push(value); + this.unsafeLast = sortedAndUnique ? value : undefined; + this.sortedAndUnique = sortedAndUnique; + } + + private ensureSortedAndUnique() { + if (!this.sortedAndUnique) { + this.unsafeArray = deduplicateSorted(stableSort(this.unsafeArray, this.relationalComparer), this.equalityComparer); + this.unsafeLast = undefined; + this.sortedAndUnique = true; + } + } + } } diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index e8e35f65f64..5e960f5fdb5 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*/ /** @@ -97,20 +98,11 @@ 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 = 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 - }); - let bundleInfo: BundleInfo = createDefaultBundleInfo(); let emitSkipped = false; let exportedModulesFromDeclarationEmit: ExportedModulesFromDeclarationEmit | undefined; @@ -169,6 +161,7 @@ namespace ts { target: compilerOptions.target, sourceMap: compilerOptions.sourceMap, inlineSourceMap: compilerOptions.inlineSourceMap, + inlineSources: compilerOptions.inlineSources, extendedDiagnostics: compilerOptions.extendedDiagnostics, }; @@ -180,18 +173,10 @@ namespace ts { // 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(); @@ -233,12 +218,6 @@ namespace ts { // 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, @@ -247,7 +226,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; @@ -270,36 +255,58 @@ 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); } - const sourceMappingURL = mapRecorder.getSourceMappingURL(); - if (sourceMappingURL) { - if (!writer.isAtStartOfLine()) writer.rawWrite(newLine); - writer.writeComment(`//# ${"sourceMappingURL"}=${sourceMappingURL}`); // Tools can sometimes see this line as a source mapping url comment + if (sourceMapGenerator) { + if (sourceMapDataList) { + sourceMapDataList.push({ + inputSourceFileNames: sourceMapGenerator.getSources(), + sourceMap: sourceMapGenerator.toJSON() + }); + } + + 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); + } } else { writer.writeLine(); } - // Write the source map - if (sourceMapFilePath) { - const sourceMap = mapRecorder.getText(); - if (sourceMap) { - writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap, /*writeByteOrderMark*/ false, sourceFiles); - } - } - // Write the output file writeFile(host, emitterDiagnostics, jsFilePath, writer.getText(), !!compilerOptions.emitBOM, sourceFiles); @@ -310,18 +317,81 @@ 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; } } @@ -335,11 +405,11 @@ namespace ts { export function createPrinter(printerOptions: PrinterOptions = {}, handlers: PrintHandlers = {}): Printer { const { hasGlobalName, - onEmitSourceMapOfNode, - onEmitSourceMapOfToken, - onEmitSourceMapOfPosition, + // onEmitSourceMapOfNode, + // onEmitSourceMapOfToken, + // onEmitSourceMapOfPosition, + // onSetSourceFile, onEmitNode, - onSetSourceFile, substituteNode, onBeforeEmitNodeArray, onAfterEmitNodeArray, @@ -347,14 +417,10 @@ namespace ts { 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. @@ -366,13 +432,25 @@ 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; - 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; + reset(); return { // public API @@ -415,12 +493,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(); } @@ -436,7 +514,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; @@ -444,7 +522,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); } @@ -453,10 +531,10 @@ 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); @@ -480,16 +558,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); @@ -518,18 +596,19 @@ namespace ts { function setSourceFile(sourceFile: SourceFile) { currentSourceFile = sourceFile; - comments.setSourceFile(sourceFile); - if (onSetSourceFile) { - onSetSourceFile(sourceFile); - } + currentLineMap = undefined; + detachedCommentsInfo = undefined; + setSourceMapSource(sourceFile); } - function setWriter(output: EmitTextWriter | undefined) { - if (output && printerOptions.omitTrailingSemicolon) { - output = getTrailingSemicolonOmittingWriter(output); + function setWriter(_writer: EmitTextWriter | undefined, _sourceMapGenerator: SourceMapGenerator | undefined) { + if (_writer && printerOptions.omitTrailingSemicolon) { + _writer = getTrailingSemicolonOmittingWriter(_writer); } - writer = output!; // TODO: GH#18217 - comments.setWriter(output!); + + writer = _writer!; // TODO: GH#18217 + sourceMapGenerator = _sourceMapGenerator; + sourceMapsDisabled = !writer || !sourceMapGenerator; } function reset() { @@ -539,8 +618,14 @@ 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) { @@ -576,7 +661,7 @@ namespace ts { return pipelineEmitWithoutComments; case PipelinePhase.SourceMaps: - if (onEmitSourceMapOfNode && hint !== EmitHint.SourceFile) { + if (!sourceMapsDisabled && hint !== EmitHint.SourceFile) { return pipelineEmitWithSourceMap; } // falls through @@ -610,7 +695,8 @@ namespace ts { function pipelineEmitWithSourceMap(hint: EmitHint, node: Node) { Debug.assert(hint !== EmitHint.SourceFile); - Debug.assertDefined(onEmitSourceMapOfNode)(hint, node, pipelineEmitWithHint); + Debug.assertDefined(sourceMapGenerator); + emitNodeWithSourceMap(hint, node, pipelineEmitWithHint); } function pipelineEmitWithHint(hint: EmitHint, node: Node): void { @@ -3066,6 +3152,8 @@ namespace ts { } } + // Writers + function writeLiteral(s: string) { writer.writeLiteral(s); } @@ -3127,8 +3215,8 @@ namespace ts { } 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); } @@ -3729,6 +3817,533 @@ namespace ts { // otherwise, return the original node for the source; return node; } + + // Comments + + function emitNodeWithComments(hint: EmitHint, node: Node, emitCallback: (hint: EmitHint, node: Node) => void) { + if (commentsDisabled) { + 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.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 emitNodeWithNestedComments(hint: EmitHint, node: Node, emitFlags: EmitFlags, emitCallback: (hint: EmitHint, node: Node) => void) { + if (emitFlags & EmitFlags.NoNestedComments) { + commentsDisabled = true; + emitCallback(hint, node); + commentsDisabled = 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 = commentsDisabled || end < 0 || (emitFlags & EmitFlags.NoTrailingComments) !== 0; + + if (!skipLeadingComments) { + emitDetachedCommentsAndUpdateCommentsInfo(detachedRange); + } + + if (extendedDiagnostics) { + performance.measure("commentTime", "preEmitBodyWithDetachedComments"); + } + + if (emitFlags & EmitFlags.NoNestedComments && !commentsDisabled) { + commentsDisabled = true; + emitCallback(node); + commentsDisabled = false; + } + else { + emitCallback(node); + } + + if (extendedDiagnostics) { + performance.mark("beginEmitBodyWithDetachedCommetns"); + } + + if (!skipTrailingComments) { + emitLeadingComments(detachedRange.end, /*isEmittedNode*/ true); + if (hasWrittenComment && !writer.isAtStartOfLine()) { + writer.writeLine(); + } + } + + if (extendedDiagnostics) { + performance.measure("commentTime", "beginEmitBodyWithDetachedCommetns"); + } + } + + function emitLeadingComments(pos: number, isEmittedNode: boolean) { + hasWrittenComment = false; + + if (isEmittedNode) { + forEachLeadingCommentToEmit(pos, emitLeadingComment); + } + else if (pos === 0) { + // If the node will not be emitted in JS, remove all the comments(normal, pinned and ///) associated with the node, + // unless it is a triple slash comment at the top of the file. + // For Example: + // /// + // declare var x; + // /// + // interface F {} + // The first /// will NOT be removed while the second one will be removed even though both node will not be emitted + forEachLeadingCommentToEmit(pos, emitTripleSlashLeadingComment); + } + } + + function emitTripleSlashLeadingComment(commentPos: number, commentEnd: number, kind: SyntaxKind, hasTrailingNewLine: boolean, rangePos: number) { + if (isTripleSlashComment(commentPos, commentEnd)) { + emitLeadingComment(commentPos, commentEnd, kind, hasTrailingNewLine, rangePos); + } + } + + function 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; + } + + 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 + + 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 (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 (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 + + /** + * Skips trivia such as comments and white-space that can optionally overriden by the source map source + */ + function skipSourceTrivia(pos: number): number { + return sourceMapSource.skipTrivia ? sourceMapSource.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); + } + + /** + * 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 (sourceMapsDisabled || isInJsonFile(node)) { + return emitCallback(hint, node); + } + + if (node) { + if (isUnparsedSource(node) && node.sourceMapText !== undefined) { + const parsed = tryParseRawSourceMap(node.sourceMapText); + if (parsed) { + sourceMapGenerator!.appendSourceMap( + writer.getLine(), + writer.getColumn(), + parsed, + node.sourceMapPath!); + } + 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 = sourceMapSource; + if (source === oldSource) source = undefined; + if (source) setSourceMapSource(source); + + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoLeadingSourceMap) === 0 + && pos >= 0) { + emitPos(skipSourceTrivia(pos)); + } + + if (source) setSourceMapSource(oldSource); + + if (emitFlags & EmitFlags.NoNestedSourceMaps) { + sourceMapsDisabled = true; + emitCallback(hint, node); + sourceMapsDisabled = false; + } + else { + emitCallback(hint, node); + } + + if (source) setSourceMapSource(source); + + if (node.kind !== SyntaxKind.NotEmittedStatement + && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 + && end >= 0) { + emitPos(end); + } + + if (source) setSourceMapSource(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 | 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]; + + 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 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/sourcemap.ts b/src/compiler/sourcemap.ts index 0c5af012b99..e33de970019 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -1,379 +1,23 @@ /* @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 | undefined; - - /** - * Gets the SourceMappingURL for the source map. - */ - getSourceMappingURL(): string | undefined; - } - - 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, writerOptions: SourceMapOptions = host.getCompilerOptions()): SourceMapWriter { - let currentSource: SourceMapSource; - let currentSourceIndex = -1; - let sourceMapDir: string; // The directory in which sourcemap will be - - // Source map data - let sourceMapData: SourceMapData; - let sourceMapDataList: SourceMapData[] | undefined; - let disabled: boolean = !(writerOptions.sourceMap || writerOptions.inlineSourceMap); - - let sourceMapGenerator: SourceMapGenerator | undefined; - let sourceMapEmitter: SourceMapEmitter | undefined; - - return { - initialize, - reset, - setSourceFile, - emitPos, - emitNodeWithSourceMap, - emitTokenWithSourceMap, - getText, - getSourceMappingURL, - }; - - /** - * 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(currentSource.text, pos); - } - - /** - * 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)) { - return; - } - - if (sourceMapData) { - reset(); - } - - sourceMapDataList = outputSourceMapDataList; - - // Initialize source map data - sourceMapData = { - sourceMapFilePath, - jsSourceMappingURL: !writerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined!, // TODO: GH#18217 - sourceMapFile: getBaseFileName(normalizeSlashes(filePath)), - sourceMapSourceRoot: writerOptions.sourceRoot || "", - sourceMapSources: [], - inputSourceFileNames: [], - sourceMapNames: [], - sourceMapMappings: "", - sourceMapSourcesContent: writerOptions.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 (writerOptions.mapRoot) { - sourceMapDir = normalizeSlashes(writerOptions.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); - } - } - else { - sourceMapDir = getDirectoryPath(normalizePath(filePath)); - } - - // If sourceroot option: Use the relative path corresponding to the common directory path - // otherwise source locations relative to map file location - const sourcesDirectoryPath = writerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir; - sourceMapGenerator = createSourceMapGenerator(host, sourceMapData, sourcesDirectoryPath, writerOptions); - sourceMapEmitter = getSourceMapEmitter(sourceMapGenerator, writer); - } - - /** - * Reset the SourceMapWriter to an empty state. - */ - function reset() { - if (disabled) { - return; - } - - // Record source map data for the test harness. - if (sourceMapDataList) { - sourceMapDataList.push(sourceMapData); - } - - currentSource = undefined!; - currentSourceIndex = -1; - sourceMapDir = undefined!; - sourceMapData = undefined!; - sourceMapDataList = undefined!; - sourceMapEmitter = undefined; - } - - /** - * 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; - } - - const { line: sourceLine, character: sourceCharacter } = getLineAndCharacterOfPosition(currentSource, pos); - Debug.assertDefined(sourceMapEmitter, "Not initialized").emitMapping(currentSourceIndex, sourceLine, sourceCharacter, /*nameIndex*/ undefined); - } - - /** - * 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 parsed = tryParseRawSourceMap(node.sourceMapText); - if (parsed) { - Debug.assertDefined(sourceMapEmitter, "Not initialized").emitSourceMap(parsed, node.sourceMapPath!); - } - 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; - - if (isJsonSourceMapSource(sourceFile)) { - return; - } - - if (!sourceMapGenerator) return Debug.fail("Not initialized"); - - currentSourceIndex = sourceMapGenerator.addSource(sourceFile.fileName); - if (writerOptions.inlineSources) { - sourceMapGenerator.setSourceContent(currentSourceIndex, sourceFile.text); - } - } - - /** - * Gets the text for the source map. - */ - function getText() { - if (disabled || isJsonSourceMapSource(currentSource)) { - return undefined; - } - - return Debug.assertDefined(sourceMapGenerator, "Not initialized").toString(); - } - - /** - * Gets the SourceMappingURL for the source map. - */ - function getSourceMappingURL() { - if (disabled || isJsonSourceMapSource(currentSource)) { - return undefined; - } - - if (writerOptions.inlineSourceMap) { - // Encode the sourceMap into the sourceMap url - const sourceMapText = Debug.assertDefined(sourceMapGenerator, "Not initialized").toString(); - const base64SourceMapText = base64encode(sys, sourceMapText); - return sourceMapData.jsSourceMappingURL = `data:application/json;base64,${base64SourceMapText}`; - } - else { - return sourceMapData.jsSourceMappingURL; - } - } - } - - interface SourceMapGeneratorOptions { - extendedDiagnostics?: boolean; - } - - function createSourceMapGenerator(host: EmitHost, sourceMapData: SourceMapData, sourcesDirectoryPath: string, generatorOptions: SourceMapGeneratorOptions): SourceMapGenerator { + 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 + const rawSources: string[] = []; + const sources: string[] = []; const sourceToSourceIndexMap = createMap(); + let sourcesContent: (string | null)[] | undefined; + + const names: string[] = []; let nameToNameIndexMap: Map | undefined; + let mappings = ""; // Last recorded and encoded mappings let lastGeneratedLine = 0; @@ -395,6 +39,7 @@ namespace ts { let hasPendingName = false; return { + getSources: () => rawSources, addSource, setSourceContent, addName, @@ -414,9 +59,9 @@ namespace ts { let sourceIndex = sourceToSourceIndexMap.get(source); if (sourceIndex === undefined) { - sourceIndex = sourceMapData.sourceMapSources.length; - sourceMapData.sourceMapSources.push(source); - sourceMapData.inputSourceFileNames.push(fileName); + sourceIndex = sources.length; + sources.push(source); + rawSources.push(fileName); sourceToSourceIndexMap.set(source, sourceIndex); } exit(); @@ -426,24 +71,23 @@ namespace ts { function setSourceContent(sourceIndex: number, content: string | null) { enter(); if (content !== null) { - if (!sourceMapData.sourceMapSourcesContent) sourceMapData.sourceMapSourcesContent = []; - while (sourceMapData.sourceMapSourcesContent.length < sourceIndex) { + if (!sourcesContent) sourcesContent = []; + while (sourcesContent.length < sourceIndex) { // tslint:disable-next-line:no-null-keyword boolean-trivia - sourceMapData.sourceMapSourcesContent.push(null); + sourcesContent.push(null); } - sourceMapData.sourceMapSourcesContent[sourceIndex] = content; + sourcesContent[sourceIndex] = content; } exit(); } function addName(name: string) { enter(); - if (!sourceMapData.sourceMapNames) sourceMapData.sourceMapNames = []; if (!nameToNameIndexMap) nameToNameIndexMap = createMap(); let nameIndex = nameToNameIndexMap.get(name); if (nameIndex === undefined) { - nameIndex = sourceMapData.sourceMapNames.length; - sourceMapData.sourceMapNames.push(name); + nameIndex = names.length; + names.push(name); nameToNameIndexMap.set(name, nameIndex); } exit(); @@ -503,7 +147,7 @@ namespace ts { // First, decode the old component sourcemap const sourceIndexToNewSourceIndexMap: number[] = []; let nameIndexToNewNameIndexMap: number[] | undefined; - const mappingIterator = sourcemaps.decodeMappings(map); + 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; @@ -524,7 +168,7 @@ namespace ts { } newSourceLine = raw.sourceLine; - newSourceCharacter = raw.sourceColumn; + newSourceCharacter = raw.sourceCharacter; if (map.names && raw.nameIndex !== undefined) { if (!nameIndexToNewNameIndexMap) nameIndexToNewNameIndexMap = []; newNameIndex = nameIndexToNewNameIndexMap[raw.nameIndex]; @@ -534,8 +178,8 @@ namespace ts { } } - const newGeneratedLine = raw.emittedLine + generatedLine; - const newGeneratedCharacter = raw.emittedLine === 0 ? raw.emittedColumn + generatedCharacter : raw.emittedColumn; + const newGeneratedLine = raw.generatedLine + generatedLine; + const newGeneratedCharacter = raw.generatedLine === 0 ? raw.generatedCharacter + generatedCharacter : raw.generatedCharacter; addMapping(newGeneratedLine, newGeneratedCharacter, newSourceIndex, newSourceLine, newSourceCharacter, newNameIndex); } exit(); @@ -551,7 +195,6 @@ namespace ts { || lastNameIndex !== pendingNameIndex; } - // Encoding for sourcemap span function commitPendingMapping() { if (!hasPending || !shouldCommitMapping()) { return; @@ -563,7 +206,7 @@ namespace ts { if (lastGeneratedLine < pendingGeneratedLine) { // Emit line delimiters do { - sourceMapData.sourceMapMappings += ";"; + mappings += ";"; lastGeneratedLine++; lastGeneratedCharacter = 0; } @@ -573,30 +216,30 @@ namespace ts { Debug.assertEqual(lastGeneratedLine, pendingGeneratedLine, "generatedLine cannot backtrack"); // Emit comma to separate the entry if (hasLast) { - sourceMapData.sourceMapMappings += ","; + mappings += ","; } } // 1. Relative generated character - sourceMapData.sourceMapMappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter); + mappings += base64VLQFormatEncode(pendingGeneratedCharacter - lastGeneratedCharacter); lastGeneratedCharacter = pendingGeneratedCharacter; if (hasPendingSource) { // 2. Relative sourceIndex - sourceMapData.sourceMapMappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex); + mappings += base64VLQFormatEncode(pendingSourceIndex - lastSourceIndex); lastSourceIndex = pendingSourceIndex; // 3. Relative source line - sourceMapData.sourceMapMappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine); + mappings += base64VLQFormatEncode(pendingSourceLine - lastSourceLine); lastSourceLine = pendingSourceLine; // 4. Relative source character - sourceMapData.sourceMapMappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter); + mappings += base64VLQFormatEncode(pendingSourceCharacter - lastSourceCharacter); lastSourceCharacter = pendingSourceCharacter; if (hasPendingName) { // 5. Relative nameIndex - sourceMapData.sourceMapMappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex); + mappings += base64VLQFormatEncode(pendingNameIndex - lastNameIndex); lastNameIndex = pendingNameIndex; } } @@ -609,36 +252,36 @@ namespace ts { 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, }; } } - function getSourceMapEmitter(generator: SourceMapGenerator, writer: EmitTextWriter): SourceMapEmitter { - if (writer.getSourceMapEmitter) { - return writer.getSourceMapEmitter(generator); - } + // 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*(\/\/[@#] .*)?$/; - return createSourceMapEmitter(generator, writer); - } - - function createSourceMapEmitter(generator: SourceMapGenerator, writer: EmitTextWriter) { - return { - emitMapping, - emitSourceMap - }; - - function emitMapping(sourceIndex?: number, sourceLine?: number, sourceCharacter?: number, nameIndex?: number) { - return generator.addMapping(writer.getLine(), writer.getColumn(), sourceIndex!, sourceLine!, sourceCharacter!, nameIndex); - } - - function emitSourceMap(sourceMap: RawSourceMap, sourceMapPath: string): void { - return generator.appendSourceMap(writer.getLine(), writer.getColumn(), sourceMap, sourceMapPath); + /** + * 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; + } } } @@ -674,14 +317,206 @@ namespace ts { return undefined; } - const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + export interface MappingsDecoder extends Iterator { + readonly pos: number; + readonly error: string | undefined; + readonly state: Required; + } - function base64FormatEncode(inValue: number) { - if (inValue < 64) { - return base64Chars.charAt(inValue); + 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 + }; } - throw TypeError(inValue + ": not a 64 based value"); + function stopIterating(): { value: never, done: true } { + done = true; + return { value: undefined!, done: true }; + } + + 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; + } + + // 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; + } + } + + 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; + } + + export function isSourceMapping(mapping: Mapping): mapping is SourceMapping { + return mapping.sourceIndex !== undefined + && mapping.sourceLine !== undefined + && mapping.sourceCharacter !== undefined; + } + + 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`); + } + + 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) { @@ -706,9 +541,176 @@ 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: T, right: T) { + return left.generatedPosition === right.generatedPosition + && left.sourceIndex === right.sourceIndex + && left.sourcePosition === right.sourcePosition; + } + + function compareSourcePositions(left: SourceMappedPosition, right: SourceMappedPosition) { + return left.sourcePosition - right.sourcePosition; + } + + function compareGeneratedPositions(left: MappedPosition, right: MappedPosition) { + return 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 generatedFilePath = toPath(map.file, mapDirectory, host.getCanonicalFileName); + const generatedFile = host.getSourceFileLike(generatedFilePath); + const sourceFilePaths = map.sources.map(source => toPath(source, sourceRoot, host.getCanonicalFileName)); + const sourceToSourceIndexMap = createMapFromEntries(sourceFilePaths.map((source, i) => [source, i] as [string, number])); + let decodedMappings: ReadonlyArray | undefined; + let generatedMappings: ReadonlyArray | 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 = sourceFilePaths[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: SortedUniqueList[] = []; + for (const mapping of getDecodedMappings()) { + if (!isSourceMappedPosition(mapping)) continue; + let list = lists[mapping.sourceIndex]; + if (!list) lists[mapping.sourceIndex] = list = new SortedUniqueList(compareSourcePositions, sameMappedPosition); + list.push(mapping); + } + sourceMappings = lists.map(list => list.toArray()); + } + return sourceMappings[sourceIndex]; + } + + function getGeneratedMappings() { + if (generatedMappings === undefined) { + const list = new SortedUniqueList(compareGeneratedPositions, sameMappedPosition); + for (const mapping of getDecodedMappings()) { + list.push(mapping); + } + generatedMappings = list.toArray(); + } + return generatedMappings; + } + + function getGeneratedPosition(loc: DocumentPosition): DocumentPosition { + const sourceIndex = sourceToSourceIndexMap.get(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 compliment of result + targetIndex = ~targetIndex; + } + + const mapping = sourceMappings[targetIndex]; + if (mapping === undefined || mapping.sourceIndex !== sourceIndex) { + return loc; + } + + return { fileName: generatedFilePath, 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 compliment of result + targetIndex = ~targetIndex; + } + + const mapping = generatedMappings[targetIndex]; + if (mapping === undefined || !isSourceMappedPosition(mapping)) { + return loc; + } + + return { fileName: sourceFilePaths[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 8cd274491e0..00000000000 --- a/src/compiler/sourcemapDecoder.ts +++ /dev/null @@ -1,367 +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 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; - } - - export function decode(host: SourceMapDecodeHost, mapPath: string, map: RawSourceMap, 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) !== 0) { - return loc; - } - return { fileName: toPath(map.file, sourceRoot, host.getCanonicalFileName), 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: toPath(maps[targetIndex].sourcePath, sourceRoot, host.getCanonicalFileName), 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.getSourceFile(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) || - 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 - }; - } - } - - /*@internal*/ - export interface MappingsDecoder extends Iterator { - readonly decodingIndex: number; - readonly error: string | undefined; - readonly lastSpan: SourceMapSpan; - } - - /*@internal*/ - export function decodeMappings(map: Pick): 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: RawSourceMap, 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/tsconfig.json b/src/compiler/tsconfig.json index 20b97c1b778..922931eea16 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -24,7 +24,7 @@ "checker.ts", "factory.ts", "visitor.ts", - "sourcemapDecoder.ts", + "sourcemap.ts", "transformers/utilities.ts", "transformers/destructuring.ts", "transformers/ts.ts", @@ -41,8 +41,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 dedbdb10bc6..d1805a536bd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2854,16 +2854,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 */ @@ -2885,7 +2879,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; } @@ -5236,8 +5230,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; } /** @@ -5317,6 +5311,7 @@ namespace ts { /*@internal*/ target?: CompilerOptions["target"]; /*@internal*/ sourceMap?: boolean; /*@internal*/ inlineSourceMap?: boolean; + /*@internal*/ inlineSources?: boolean; /*@internal*/ extendedDiagnostics?: boolean; /*@internal*/ onlyPrintJsDocStyle?: boolean; } @@ -5334,9 +5329,10 @@ namespace ts { /** * Generates a source map. - * @internal */ + /* @internal */ export interface SourceMapGenerator { + getSources(): ReadonlyArray; /** * Adds a source to the source map. */ @@ -5371,23 +5367,26 @@ namespace ts { toString(): string; } + /* @internal */ + export interface DocumentPositionMapperHost { + getSourceFileLike(path: Path): SourceFileLike | undefined; + getCanonicalFileName(path: string): string; + log?(text: string): void; + } + /** - * Emits SourceMap information through an entangled `EmitTextWriter` - * @internal + * Maps positions between source and generated files. */ - export interface SourceMapEmitter { - /** - * Emits a mapping without source information. - */ - emitMapping(): void; - /** - * Emits a mapping with source information. - */ - emitMapping(sourceIndex: number, sourceLine: number, sourceCharacter: number, nameIndex?: number): void; - /** - * Emits a source map. - */ - emitSourceMap(sourceMap: RawSourceMap, sourceMapPath: string): void; + /* @internal */ + export interface DocumentPositionMapper { + getSourcePosition(input: DocumentPosition): DocumentPosition; + getGeneratedPosition(input: DocumentPosition): DocumentPosition; + } + + /* @internal */ + export interface DocumentPosition { + fileName: string; + pos: number; } /* @internal */ @@ -5403,7 +5402,6 @@ namespace ts { getColumn(): number; getIndent(): number; isAtStartOfLine(): boolean; - getSourceMapEmitter?(generator: SourceMapGenerator): SourceMapEmitter; } export interface GetEffectiveTypeRootsHost { diff --git a/src/harness/sourceMapRecorder.ts b/src/harness/sourceMapRecorder.ts index 65236e78cb7..92c3e2d4a94 100644 --- a/src/harness/sourceMapRecorder.ts +++ b/src/harness/sourceMapRecorder.ts @@ -1,40 +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({ - sources: sourceMapData.sourceMapSources, - 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() { @@ -45,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; @@ -57,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; @@ -71,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]; } @@ -117,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 = []; @@ -125,9 +117,9 @@ namespace Harness.SourceMapRecorder { spansOnSingleLine.push({ sourceMapSpan, decodeErrors }); } - export function recordNewSourceFileSpan(sourceMapSpan: ts.SourceMapSpan, newSourceFileCode: string) { + export function recordNewSourceFileSpan(sourceMapSpan: ts.Mapping, newSourceFileCode: string) { let continuesLine = false; - if (spansOnSingleLine.length > 0 && spansOnSingleLine[0].sourceMapSpan.emittedLine === sourceMapSpan.emittedLine) { + if (spansOnSingleLine.length > 0 && spansOnSingleLine[0].sourceMapSpan.generatedCharacter === sourceMapSpan.generatedLine) { writeRecordedSpans(); spansOnSingleLine = []; nextJsLineToWrite--; // walk back one line to reprint the line @@ -138,8 +130,8 @@ namespace Harness.SourceMapRecorder { assert.isTrue(spansOnSingleLine.length === 1); sourceMapRecorder.WriteLine("-------------------------------------------------------------------"); - sourceMapRecorder.WriteLine("emittedFile:" + jsFile.file + (continuesLine ? ` (${sourceMapSpan.emittedLine + 1}, ${sourceMapSpan.emittedColumn + 1})` : "")); - 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); @@ -198,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; } } @@ -209,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); @@ -226,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 @@ -258,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); @@ -283,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 } @@ -308,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 117887d7611..583a1954e7f 100644 --- a/src/server/editorServices.ts +++ b/src/server/editorServices.ts @@ -2131,8 +2131,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 8d2fbe9e190..91e01f9c9b4 100644 --- a/src/server/session.ts +++ b/src/server/session.ts @@ -296,7 +296,7 @@ namespace ts.server { defaultProject: Project, projectService: ProjectService, action: (project: Project) => ReadonlyArray, - getLocation: (t: T) => sourcemaps.SourceMappableLocation, + getLocation: (t: T) => DocumentPosition, resultsEqual: (a: T, b: T) => boolean, ): T[] { const outputs: T[] = []; @@ -317,12 +317,12 @@ namespace ts.server { } function combineProjectOutputForRenameLocations( - projects: Projects, defaultProject: Project, initialLocation: sourcemaps.SourceMappableLocation, projectService: ProjectService, findInStrings: boolean, findInComments: boolean + projects: Projects, defaultProject: Project, initialLocation: DocumentPosition, projectService: ProjectService, findInStrings: boolean, findInComments: boolean ): ReadonlyArray { const outputs: RenameLocation[] = []; - combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => { - for (const output of project.getLanguageService().findRenameLocations(location.fileName, location.position, findInStrings, findInComments) || emptyArray) { + combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => { + 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); } @@ -332,17 +332,17 @@ 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, projectService: ProjectService): ReadonlyArray { + function combineProjectOutputForReferences(projects: Projects, defaultProject: Project, initialLocation: DocumentPosition, projectService: ProjectService): ReadonlyArray { const outputs: ReferencedSymbol[] = []; - combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => { - for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.position) || emptyArray) { + combineProjectOutputWorker(projects, defaultProject, initialLocation, projectService, ({ project, location }, tryAddToTodo) => { + for (const outputReferencedSymbol of project.getLanguageService().findReferences(location.fileName, location.pos) || emptyArray) { let symbolToAddTo = find(outputs, o => documentSpansEqual(o.definition, outputReferencedSymbol.definition)); if (!symbolToAddTo) { symbolToAddTo = { definition: outputReferencedSymbol.definition, references: [] }; @@ -360,7 +360,7 @@ namespace ts.server { return outputs.filter(o => o.references.length !== 0); } - interface ProjectAndLocation { + interface ProjectAndLocation { readonly project: Project; readonly location: TLocation; } @@ -378,19 +378,19 @@ namespace ts.server { } } - function combineProjectOutputWorker( + function combineProjectOutputWorker( projects: Projects, defaultProject: Project, initialLocation: TLocation, projectService: ProjectService, - cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void, - getDefinition: (() => sourcemaps.SourceMappableLocation | undefined) | undefined, + cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: DocumentPosition) => boolean) => void, + getDefinition: (() => DocumentPosition | undefined) | undefined, ): void { 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); }); @@ -411,18 +411,18 @@ 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, seenProjects: Map, - cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: sourcemaps.SourceMappableLocation) => boolean) => void, + cb: (where: ProjectAndLocation, getMappedLocation: (project: Project, location: DocumentPosition) => boolean) => void, ): ProjectAndLocation[] | undefined { if (projectAndLocation.project.getCancellationToken().isCancellationRequested()) return undefined; // Skip rest of toDo if cancelled cb(projectAndLocation, (project, location) => { @@ -447,16 +447,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; } @@ -864,7 +864,7 @@ namespace ts.server { kind: info.kind, name: info.name, textSpan: { - start: newLoc.position, + start: newLoc.pos, length: info.textSpan.length }, originalFileName: info.fileName, @@ -961,7 +961,7 @@ namespace ts.server { kind: info.kind, displayParts: info.displayParts, textSpan: { - start: newLoc.position, + start: newLoc.pos, length: info.textSpan.length }, originalFileName: info.fileName, @@ -1143,14 +1143,14 @@ namespace ts.server { private getRenameLocations(args: protocol.RenameRequestArgs, simplifiedResult: boolean): protocol.RenameResponseBody | ReadonlyArray { const file = toNormalizedPath(args.file); - const position = this.getPositionInFile(args, file); + const pos = this.getPositionInFile(args, file); const projects = this.getProjects(args); - const locations = combineProjectOutputForRenameLocations(projects, this.getDefaultProject(args), { fileName: args.file, position }, this.projectService, !!args.findInStrings, !!args.findInComments); + const locations = combineProjectOutputForRenameLocations(projects, this.getDefaultProject(args), { fileName: args.file, pos }, this.projectService, !!args.findInStrings, !!args.findInComments); if (!simplifiedResult) return locations; const defaultProject = this.getDefaultProject(args); - const renameInfo = Session.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, position)); + const renameInfo = Session.mapRenameInfo(defaultProject.getLanguageService().getRenameInfo(file, pos)); return { info: renameInfo, locs: this.toSpanGroups(locations) }; } @@ -1173,7 +1173,7 @@ namespace ts.server { const file = toNormalizedPath(args.file); const projects = this.getProjects(args); const position = this.getPositionInFile(args, file); - const references = combineProjectOutputForReferences(projects, this.getDefaultProject(args), { fileName: args.file, position }, this.projectService); + const references = combineProjectOutputForReferences(projects, this.getDefaultProject(args), { fileName: args.file, pos: position }, this.projectService); if (simplifiedResult) { const defaultProject = this.getDefaultProject(args); diff --git a/src/services/getEditsForFileRename.ts b/src/services/getEditsForFileRename.ts index bd00ce5dd9e..a2e34c72ca8 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 8ad5385a408..6bccd73965e 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; } @@ -20,44 +17,43 @@ namespace ts { getProgram: () => Program, ): SourceMapper { let sourcemappedFileCache: SourceFileLikeCache; - return { tryGetOriginalLocation, tryGetGeneratedLocation, toLineColumnOffset, clearCache }; + return { tryGetSourcePosition, tryGetGeneratedPosition, toLineColumnOffset, clearCache }; function scanForSourcemapURL(fileName: string) { const mappedFile = sourcemappedFileCache.get(toPath(fileName, currentDirectory, getCanonicalFileName)); 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) { + 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 + const program = getProgram(); + return file.sourceMapper = createDocumentPositionMapper({ + getSourceFileLike: s => { + // Lookup file in program, if provided + const file = program && program.getSourceFile(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, map, 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; @@ -85,19 +81,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 declarationPath = getDeclarationEmitOutputFilePathWorker(info.fileName, program.getCompilerOptions(), currentDirectory, program.getCommonSourceDirectory(), getCanonicalFileName); if (declarationPath === undefined) return undefined; @@ -121,4 +117,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/types.ts b/src/services/types.ts index 51c98724c09..63d8f29efbe 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/testRunner/projectsRunner.ts b/src/testRunner/projectsRunner.ts index d0663533dce..21f33de66d2 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 { @@ -317,11 +317,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) + }; } } diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index 49bc01284ba..5e43707ad7b 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1836,17 +1836,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, diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d3b20cb3f06..b1134e64dec 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1836,17 +1836,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,