diff --git a/src/compiler/factory.ts b/src/compiler/factory.ts index 6085cc550c4..111be243769 100644 --- a/src/compiler/factory.ts +++ b/src/compiler/factory.ts @@ -2585,16 +2585,19 @@ namespace ts { return node; } - export function createUnparsedSourceFile(text: string): UnparsedSource { + export function createUnparsedSourceFile(text: string, map?: string): UnparsedSource { const node = createNode(SyntaxKind.UnparsedSource); node.text = text; + node.sourceMapText = map; return node; } - export function createInputFiles(javascript: string, declaration: string): InputFiles { + export function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles { const node = createNode(SyntaxKind.InputFiles); node.javascriptText = javascript; + node.javascriptMapText = javascriptMapText; node.declarationText = declaration; + node.declarationMapText = declarationMapText; return node; } diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 4dfb9039ccf..2c8a702e139 100755 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -1192,8 +1192,10 @@ namespace ts { const dtsFilename = changeExtension(resolvedRefOpts.options.outFile, ".d.ts"); const js = host.readFile(resolvedRefOpts.options.outFile) || `/* Input file ${resolvedRefOpts.options.outFile} was missing */\r\n`; + const jsMap = host.readFile(resolvedRefOpts.options.outFile + ".map"); // TODO: try to read sourceMappingUrl comment from the js file const dts = host.readFile(dtsFilename) || `/* Input file ${dtsFilename} was missing */\r\n`; - const node = createInputFiles(js, dts); + const dtsMap = host.readFile(dtsFilename + ".map"); + const node = createInputFiles(js, dts, jsMap, dtsMap); nodes.push(node); } } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts index a274404f320..1f8723b9c80 100644 --- a/src/compiler/sourcemap.ts +++ b/src/compiler/sourcemap.ts @@ -99,6 +99,10 @@ namespace ts { let sourceMapDataList: SourceMapData[] | undefined; let disabled: boolean = !(compilerOptions.sourceMap || compilerOptions.inlineSourceMap); + let completedSections: SourceMapSectionDefinition[]; + let sectionStartLine: number; + let sectionStartColumn: number; + return { initialize, reset, @@ -146,6 +150,9 @@ namespace ts { lastEncodedNameIndex = 0; // Initialize source map data + completedSections = []; + sectionStartLine = 0; + sectionStartColumn = 0; sourceMapData = { sourceMapFilePath, jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined!, // TODO: GH#18217 @@ -214,6 +221,68 @@ namespace ts { lastEncodedNameIndex = undefined; sourceMapData = undefined!; sourceMapDataList = undefined!; + completedSections = undefined!; + sectionStartLine = undefined!; + sectionStartColumn = undefined!; + } + + interface SourceMapSection { + version: 3; + file: string; + sourceRoot?: string; + sources: string[]; + names?: string[]; + mappings: string; + sourcesContent?: string[]; + sections?: undefined; + } + + type SourceMapSectionDefinition = + | { offset: { line: number, column: number }, url: string } // Included for completeness + | { offset: { line: number, column: number }, map: SourceMap }; + + interface SectionalSourceMap { + version: 3; + file: string; + sections: SourceMapSectionDefinition[]; + } + + type SourceMap = SectionalSourceMap | SourceMapSection; + + function captureSection(): SourceMapSection { + return { + version: 3, + file: sourceMapData.sourceMapFile, + sourceRoot: sourceMapData.sourceMapSourceRoot, + sources: sourceMapData.sourceMapSources, + names: sourceMapData.sourceMapNames, + mappings: sourceMapData.sourceMapMappings, + sourcesContent: sourceMapData.sourceMapSourcesContent, + }; + } + + function resetSectionalData(): void { + sourceMapData.sourceMapSources = []; + sourceMapData.sourceMapNames = []; + sourceMapData.sourceMapMappings = ""; + sourceMapData.sourceMapSourcesContent = compilerOptions.inlineSources ? [] : undefined; + } + + function generateMap(): SourceMap { + if (completedSections.length) { + const last = { + offset: { line: sectionStartLine, column: sectionStartColumn }, + map: captureSection() + }; + return { + version: 3, + file: last.map.file, + sections: [...completedSections, last] + }; + } + else { + return captureSection(); + } } // Encoding for sourcemap span @@ -284,8 +353,8 @@ namespace ts { sourceLinePos.line++; sourceLinePos.character++; - const emittedLine = writer.getLine(); - const emittedColumn = writer.getColumn(); + const emittedLine = writer.getLine() - sectionStartLine; + const emittedColumn = emittedLine === 0 ? writer.getColumn() - sectionStartColumn : writer.getColumn(); // If this location wasn't recorded or the location in source is going backwards, record the span if (!lastRecordedSourceMapSpan || @@ -333,6 +402,38 @@ namespace ts { } if (node) { + if (isUnparsedSource(node) && node.sourceMapText !== undefined) { + if (lastRecordedSourceMapSpan && lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) { // If we've recorded some spans, save them + completedSections.push({ offset: { line: sectionStartLine, column: sectionStartColumn }, map: captureSection() }); + resetSectionalData(); + } + const text = node.sourceMapText; + let parsed: {} | undefined; + try { + parsed = JSON.parse(text); + } + catch { + // empty + } + const offset = { line: writer.getLine(), column: writer.getColumn() }; + completedSections.push(parsed + ? { + offset, + map: parsed as SourceMap + } + : { + offset, + // This is just passes the buck on sourcemaps we don't really understand, instead of issuing an error (which would be difficult this late) + url: `data:application/json;charset=utf-8;base64,${base64encode(sys, text)}` + } + ); + const emitResult = emitCallback(hint, node); + sectionStartLine = writer.getLine(); + sectionStartColumn = writer.getColumn(); + lastRecordedSourceMapSpan = undefined!; + lastEncodedSourceMapSpan = undefined!; + return emitResult; + } const emitNode = node.emitNode; const emitFlags = emitNode && emitNode.flags || EmitFlags.None; const range = emitNode && emitNode.sourceMapRange; @@ -452,15 +553,7 @@ namespace ts { encodeLastRecordedSourceMapSpan(); - return JSON.stringify({ - version: 3, - file: sourceMapData.sourceMapFile, - sourceRoot: sourceMapData.sourceMapSourceRoot, - sources: sourceMapData.sourceMapSources, - names: sourceMapData.sourceMapNames, - mappings: sourceMapData.sourceMapMappings, - sourcesContent: sourceMapData.sourceMapSourcesContent, - }); + return JSON.stringify(generateMap()); } /** diff --git a/src/compiler/transformers/declarations.ts b/src/compiler/transformers/declarations.ts index a92448e5290..3469ef125a2 100644 --- a/src/compiler/transformers/declarations.ts +++ b/src/compiler/transformers/declarations.ts @@ -180,7 +180,7 @@ namespace ts { } ), mapDefined(node.prepends, prepend => { if (prepend.kind === SyntaxKind.InputFiles) { - return createUnparsedSourceFile(prepend.declarationText); + return createUnparsedSourceFile(prepend.declarationText, prepend.declarationMapText); } })); bundle.syntheticFileReferences = []; diff --git a/src/compiler/transformers/ts.ts b/src/compiler/transformers/ts.ts index cc244d92204..03bafc314c7 100644 --- a/src/compiler/transformers/ts.ts +++ b/src/compiler/transformers/ts.ts @@ -100,7 +100,7 @@ namespace ts { function transformBundle(node: Bundle) { return createBundle(node.sourceFiles.map(transformSourceFile), mapDefined(node.prepends, prepend => { if (prepend.kind === SyntaxKind.InputFiles) { - return createUnparsedSourceFile(prepend.javascriptText); + return createUnparsedSourceFile(prepend.javascriptText, prepend.javascriptMapText); } return prepend; })); diff --git a/src/compiler/types.ts b/src/compiler/types.ts index b13f4aea613..5275cdb7dfd 100644 --- a/src/compiler/types.ts +++ b/src/compiler/types.ts @@ -2652,12 +2652,15 @@ namespace ts { export interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } export interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } export interface JsonSourceFile extends SourceFile { diff --git a/src/compiler/utilities.ts b/src/compiler/utilities.ts index 426c0d8d1da..48289a43836 100644 --- a/src/compiler/utilities.ts +++ b/src/compiler/utilities.ts @@ -5454,6 +5454,10 @@ namespace ts { return node.kind === SyntaxKind.Bundle; } + export function isUnparsedSource(node: Node): node is UnparsedSource { + return node.kind === SyntaxKind.UnparsedSource; + } + // JSDoc export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { diff --git a/tests/baselines/reference/api/tsserverlibrary.d.ts b/tests/baselines/reference/api/tsserverlibrary.d.ts index a6657b4d341..aa40d060bda 100644 --- a/tests/baselines/reference/api/tsserverlibrary.d.ts +++ b/tests/baselines/reference/api/tsserverlibrary.d.ts @@ -1669,11 +1669,14 @@ declare namespace ts { interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } interface JsonSourceFile extends SourceFile { statements: NodeArray; @@ -3371,6 +3374,7 @@ declare namespace ts { function isEnumMember(node: Node): node is EnumMember; function isSourceFile(node: Node): node is SourceFile; function isBundle(node: Node): node is Bundle; + function isUnparsedSource(node: Node): node is UnparsedSource; function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; function isJSDocUnknownType(node: Node): node is JSDocUnknownType; @@ -3826,8 +3830,8 @@ declare namespace ts { function createCommaList(elements: ReadonlyArray): CommaListExpression; function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; function createBundle(sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; - function createUnparsedSourceFile(text: string): UnparsedSource; - function createInputFiles(javascript: string, declaration: string): InputFiles; + function createUnparsedSourceFile(text: string, map?: string): UnparsedSource; + function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles; function updateBundle(node: Bundle, sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray): CallExpression; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray, param: ParameterDeclaration, paramValue: Expression): CallExpression; diff --git a/tests/baselines/reference/api/typescript.d.ts b/tests/baselines/reference/api/typescript.d.ts index d71bb5117ba..25f29c4f479 100644 --- a/tests/baselines/reference/api/typescript.d.ts +++ b/tests/baselines/reference/api/typescript.d.ts @@ -1669,11 +1669,14 @@ declare namespace ts { interface InputFiles extends Node { kind: SyntaxKind.InputFiles; javascriptText: string; + javascriptMapText?: string; declarationText: string; + declarationMapText?: string; } interface UnparsedSource extends Node { kind: SyntaxKind.UnparsedSource; text: string; + sourceMapText?: string; } interface JsonSourceFile extends SourceFile { statements: NodeArray; @@ -3371,6 +3374,7 @@ declare namespace ts { function isEnumMember(node: Node): node is EnumMember; function isSourceFile(node: Node): node is SourceFile; function isBundle(node: Node): node is Bundle; + function isUnparsedSource(node: Node): node is UnparsedSource; function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression; function isJSDocAllType(node: JSDocAllType): node is JSDocAllType; function isJSDocUnknownType(node: Node): node is JSDocUnknownType; @@ -3826,8 +3830,8 @@ declare namespace ts { function createCommaList(elements: ReadonlyArray): CommaListExpression; function updateCommaList(node: CommaListExpression, elements: ReadonlyArray): CommaListExpression; function createBundle(sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; - function createUnparsedSourceFile(text: string): UnparsedSource; - function createInputFiles(javascript: string, declaration: string): InputFiles; + function createUnparsedSourceFile(text: string, map?: string): UnparsedSource; + function createInputFiles(javascript: string, declaration: string, javascriptMapText?: string, declarationMapText?: string): InputFiles; function updateBundle(node: Bundle, sourceFiles: ReadonlyArray, prepends?: ReadonlyArray): Bundle; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray): CallExpression; function createImmediatelyInvokedFunctionExpression(statements: ReadonlyArray, param: ParameterDeclaration, paramValue: Expression): CallExpression;