diff --git a/src/compiler/core.ts b/src/compiler/core.ts index 1792a393234..747801f83bd 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -2249,6 +2249,7 @@ namespace ts { getSymbolConstructor(): new (flags: SymbolFlags, name: string) => Symbol; getTypeConstructor(): new (checker: TypeChecker, flags: TypeFlags) => Type; getSignatureConstructor(): new (checker: TypeChecker) => Signature; + getSourceMapSourceConstructor(): new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; } function Symbol(this: Symbol, flags: SymbolFlags, name: string) { @@ -2279,6 +2280,12 @@ namespace ts { this.original = undefined; } + function SourceMapSource(this: SourceMapSource, fileName: string, text: string, skipTrivia?: (pos: number) => number) { + this.fileName = fileName; + this.text = text; + this.skipTrivia = skipTrivia || (pos => pos); + } + export let objectAllocator: ObjectAllocator = { getNodeConstructor: () => Node, getTokenConstructor: () => Node, @@ -2286,7 +2293,8 @@ namespace ts { getSourceFileConstructor: () => Node, getSymbolConstructor: () => Symbol, getTypeConstructor: () => Type, - getSignatureConstructor: () => Signature + getSignatureConstructor: () => Signature, + getSourceMapSourceConstructor: () => SourceMapSource, }; export const enum AssertionLevel { diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 12e8110e7be..aaf7d958072 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2314,15 +2314,24 @@ namespace ts { /** * Sets a custom text range to use when emitting source maps. */ - export function setSourceMapRange(node: T, range: TextRange | undefined) { + export function setSourceMapRange(node: T, range: SourceMapRange | undefined) { getOrCreateEmitNode(node).sourceMapRange = range; return node; } + let SourceMapSource: new (fileName: string, text: string, skipTrivia?: (pos: number) => number) => SourceMapSource; + + /** + * Create an external source map source file reference + */ + export function createSourceMapSource(fileName: string, text: string, skipTrivia?: (pos: number) => number): SourceMapSource { + return new (SourceMapSource || (SourceMapSource = objectAllocator.getSourceMapSourceConstructor()))(fileName, text, skipTrivia); + } + /** * Gets the TextRange to use for source maps for a token of a node. */ - export function getTokenSourceMapRange(node: Node, token: SyntaxKind): TextRange | undefined { + export function getTokenSourceMapRange(node: Node, token: SyntaxKind): SourceMapRange | undefined { const emitNode = node.emitNode; const tokenSourceMapRanges = emitNode && emitNode.tokenSourceMapRanges; return tokenSourceMapRanges && tokenSourceMapRanges[token]; @@ -2331,7 +2340,7 @@ namespace ts { /** * Sets the TextRange to use for source maps for a token of a node. */ - export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: TextRange | undefined) { + export function setTokenSourceMapRange(node: T, token: SyntaxKind, range: SourceMapRange | undefined) { const emitNode = getOrCreateEmitNode(node); const tokenSourceMapRanges = emitNode.tokenSourceMapRanges || (emitNode.tokenSourceMapRanges = []); tokenSourceMapRanges[token] = range; diff --git a/src/compiler/scanner.ts b/src/compiler/scanner.ts index 68a36c40a80..dd43b3c8263 100644 --- a/src/compiler/scanner.ts +++ b/src/compiler/scanner.ts @@ -363,7 +363,7 @@ namespace ts { }; } - export function getLineAndCharacterOfPosition(sourceFile: SourceFile, position: number): LineAndCharacter { + export function getLineAndCharacterOfPosition(sourceFile: SourceFileLike, position: number): LineAndCharacter { return computeLineAndCharacterOfPosition(getLineStarts(sourceFile), position); } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index d743f488e75..ffef485581b 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -22,7 +22,7 @@ namespace ts { * * @param sourceFile The source file. */ - setSourceFile(sourceFile: SourceFile): void; + setSourceFile(sourceFile: SourceMapSource): void; /** * Emits a mapping. @@ -81,7 +81,7 @@ namespace ts { export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter { const compilerOptions = host.getCompilerOptions(); const extendedDiagnostics = compilerOptions.extendedDiagnostics; - let currentSourceFile: SourceFile; + let currentSource: SourceMapSource; let currentSourceText: string; let sourceMapDir: string; // The directory in which sourcemap will be @@ -109,6 +109,13 @@ namespace ts { 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(currentSourceText, pos); + } + /** * Initialize the SourceMapWriter for a new output file. * @@ -125,7 +132,7 @@ namespace ts { reset(); } - currentSourceFile = undefined; + currentSource = undefined; currentSourceText = undefined; // Current source map file and its index in the sources list @@ -192,7 +199,7 @@ namespace ts { return; } - currentSourceFile = undefined; + currentSource = undefined; sourceMapDir = undefined; sourceMapSourceIndex = undefined; lastRecordedSourceMapSpan = undefined; @@ -263,7 +270,7 @@ namespace ts { performance.mark("beforeSourcemap"); } - const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); + const sourceLinePos = getLineAndCharacterOfPosition(currentSource, pos); // Convert the location to be one-based. sourceLinePos.line++; @@ -320,14 +327,22 @@ namespace ts { if (node) { const emitNode = node.emitNode; const emitFlags = emitNode && emitNode.flags; - const { pos, end } = emitNode && emitNode.sourceMapRange || node; + 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(skipTrivia(currentSourceText, pos)); + emitPos(skipSourceTrivia(pos)); } + if (source) setSourceFile(oldSource); + if (emitFlags & EmitFlags.NoNestedSourceMaps) { disabled = true; emitCallback(hint, node); @@ -337,11 +352,15 @@ namespace ts { emitCallback(hint, node); } + if (source) setSourceFile(source); + if (node.kind !== SyntaxKind.NotEmittedStatement && (emitFlags & EmitFlags.NoTrailingSourceMap) === 0 && end >= 0) { emitPos(end); } + + if (source) setSourceFile(oldSource); } } @@ -362,7 +381,7 @@ namespace ts { const emitFlags = emitNode && emitNode.flags; const range = emitNode && emitNode.tokenSourceMapRanges && emitNode.tokenSourceMapRanges[token]; - tokenPos = skipTrivia(currentSourceText, range ? range.pos : tokenPos); + tokenPos = skipSourceTrivia(range ? range.pos : tokenPos); if ((emitFlags & EmitFlags.NoTokenLeadingSourceMaps) === 0 && tokenPos >= 0) { emitPos(tokenPos); } @@ -382,13 +401,13 @@ namespace ts { * * @param sourceFile The source file. */ - function setSourceFile(sourceFile: SourceFile) { + function setSourceFile(sourceFile: SourceMapSource) { if (disabled) { return; } - currentSourceFile = sourceFile; - currentSourceText = currentSourceFile.text; + currentSource = sourceFile; + currentSourceText = currentSource.text; // Add the file to tsFilePaths // If sourceroot option: Use the relative path corresponding to the common directory path @@ -396,7 +415,7 @@ namespace ts { const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir; const source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, - currentSourceFile.fileName, + currentSource.fileName, host.getCurrentDirectory(), host.getCanonicalFileName, /*isAbsolutePathAnUrl*/ true); @@ -407,10 +426,10 @@ namespace ts { sourceMapData.sourceMapSources.push(source); // The one that can be used from program to get the actual source file - sourceMapData.inputSourceFileNames.push(currentSourceFile.fileName); + sourceMapData.inputSourceFileNames.push(currentSource.fileName); if (compilerOptions.inlineSources) { - sourceMapData.sourceMapSourcesContent.push(currentSourceFile.text); + sourceMapData.sourceMapSourcesContent.push(currentSource.text); } } } diff --git a/src/compiler/types.ts b/src/compiler/types.ts index ab04f72faf0..5efa10ef437 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -4013,18 +4013,29 @@ namespace ts { ES2015FunctionSyntaxMask = ContainsCapturedLexicalThis | ContainsDefaultValueAssignments, } + export interface SourceMapRange extends TextRange { + source?: SourceMapSource; + } + + export interface SourceMapSource { + fileName: string; + text: string; + /* @internal */ lineMap: number[]; + skipTrivia?: (pos: number) => number; + } + /* @internal */ export interface EmitNode { - annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup. - flags?: EmitFlags; // Flags that customize emit - leadingComments?: SynthesizedComment[]; // Synthesized leading comments + annotatedNodes?: Node[]; // Tracks Parse-tree nodes with EmitNodes for eventual cleanup. + flags?: EmitFlags; // Flags that customize emit + leadingComments?: SynthesizedComment[]; // Synthesized leading comments trailingComments?: SynthesizedComment[]; // Synthesized trailing comments - commentRange?: TextRange; // The text range to use when emitting leading or trailing comments - sourceMapRange?: TextRange; // The text range to use when emitting leading or trailing source mappings - tokenSourceMapRanges?: TextRange[]; // The text range to use when emitting source mappings for tokens - constantValue?: string | number; // The constant value of an expression - externalHelpersModuleName?: Identifier; // The local name for an imported helpers module - helpers?: EmitHelper[]; // Emit helpers for the node + commentRange?: TextRange; // The text range to use when emitting leading or trailing comments + sourceMapRange?: SourceMapRange; // The text range to use when emitting leading or trailing source mappings + tokenSourceMapRanges?: SourceMapRange[]; // The text range to use when emitting source mappings for tokens + constantValue?: string | number; // The constant value of an expression + externalHelpersModuleName?: Identifier; // The local name for an imported helpers module + helpers?: EmitHelper[]; // Emit helpers for the node } export const enum EmitFlags { diff --git a/src/services/services.ts b/src/services/services.ts index 21c756a9acc..baba64586e9 100644 --- a/src/services/services.ts +++ b/src/services/services.ts @@ -732,6 +732,15 @@ namespace ts { } } + class SourceMapSourceObject implements SourceMapSource { + lineMap: number[]; + constructor (public fileName: string, public text: string, public skipTrivia?: (pos: number) => number) {} + + public getLineAndCharacterOfPosition(pos: number): LineAndCharacter { + return ts.getLineAndCharacterOfPosition(this, pos); + } + } + function getServicesObjectAllocator(): ObjectAllocator { return { getNodeConstructor: () => NodeObject, @@ -742,6 +751,7 @@ namespace ts { getSymbolConstructor: () => SymbolObject, getTypeConstructor: () => TypeObject, getSignatureConstructor: () => SignatureObject, + getSourceMapSourceConstructor: () => SourceMapSourceObject, }; } diff --git a/src/services/types.ts b/src/services/types.ts index e042276e650..49fe2262605 100644 --- a/src/services/types.ts +++ b/src/services/types.ts @@ -71,6 +71,10 @@ namespace ts { getLineAndCharacterOfPosition(pos: number): LineAndCharacter; } + export interface SourceMapSource { + getLineAndCharacterOfPosition(pos: number): LineAndCharacter; + } + /** * Represents an immutable snapshot of a script at a specified time.Once acquired, the * snapshot is observably immutable. i.e. the same calls with the same parameters will return