From 21574cd8a64f0d8f695cb032528cfea6425ba2cf Mon Sep 17 00:00:00 2001 From: Ron Buckton Date: Wed, 28 Oct 2015 17:24:24 -0700 Subject: [PATCH] Added source maps to printer --- Jakefile.js | 2 + src/compiler/core.ts | 16 ++ src/compiler/emitter.ts | 87 ++++++- src/compiler/factory.generated.ts | 65 +++-- src/compiler/printer.ts | 47 ++-- src/compiler/program.ts | 18 +- src/compiler/sourcemap.ts | 404 ++++++++++++++++++++++++++++++ src/compiler/tsconfig.json | 3 + 8 files changed, 593 insertions(+), 49 deletions(-) create mode 100644 src/compiler/sourcemap.ts diff --git a/Jakefile.js b/Jakefile.js index 56886ecc211..fa38008590a 100644 --- a/Jakefile.js +++ b/Jakefile.js @@ -52,6 +52,7 @@ var compilerSources = [ "transforms/jsx.ts", "transforms/es6.ts", "declarationEmitter.ts", + "sourcemap.ts", "printer.ts", "emitter.ts", "program.ts", @@ -82,6 +83,7 @@ var servicesSources = [ "transforms/jsx.ts", "transforms/es6.ts", "declarationEmitter.ts", + "sourcemap.ts", "printer.ts", "emitter.ts", "program.ts", diff --git a/src/compiler/core.ts b/src/compiler/core.ts index df8f56d6654..cc47039ae9c 100644 --- a/src/compiler/core.ts +++ b/src/compiler/core.ts @@ -436,6 +436,22 @@ namespace ts { return result; } + export function reduceProperties(map: Map, f: (a: U, v: T, k: string) => U, initial: U): U { + let result = initial; + if (map) { + for (let key in map) { + if (hasProperty(map, key)) { + result = f(result, map[key], String(key)); + } + } + } + return result; + } + + export function isArray(value: any): value is any[] { + return Array.isArray ? Array.isArray(value) : Object.prototype.toString.call(value) === "[object Array]"; + } + export function memoize(callback: () => T): () => T { let value: T; return () => { diff --git a/src/compiler/emitter.ts b/src/compiler/emitter.ts index 9cda785b449..0001d1c3044 100644 --- a/src/compiler/emitter.ts +++ b/src/compiler/emitter.ts @@ -7603,9 +7603,20 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi function emitFile(jsFilePath: string, sourceFile?: SourceFile) { if (compilerOptions.experimentalTransforms) { + let writer = createTextWriter(host.getNewLine()); let nodes = sourceFile ? [sourceFile] : host.getSourceFiles(); - let text = printFiles(resolver, host, filter(nodes, isNonDeclarationFile), getTransformationChain(compilerOptions)); - writeFile(host, diagnostics, jsFilePath, text, compilerOptions.emitBOM); + let transformationChain = getTransformationChain(compilerOptions); + let sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap + ? createSourceMapWriter(host, writer, jsFilePath, sourceFile) + : getNullSourceMapWriter(); + + printFiles(resolver, host, writer, filter(nodes, isNonDeclarationFile), transformationChain, sourceMap); + + if (compilerOptions.sourceMap) { + writeFile(host, diagnostics, jsFilePath + ".map", sourceMap.getText(), /*writeByteOrderMark*/ false); + } + + writeFile(host, diagnostics, jsFilePath, writer.getText(), compilerOptions.emitBOM); } else { emitJavaScript(jsFilePath, sourceFile); @@ -7616,4 +7627,76 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi } } } + + // targetSourceFile is when users only want one file in entire project to be emitted. This is used in compileOnSave feature + // @internal + export function emitFilesUsingTransforms(resolver: EmitResolver, host: EmitHost, targetSourceFile: SourceFile): EmitResult { + let compilerOptions = host.getCompilerOptions(); + let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined; + let diagnostics: Diagnostic[] = []; + let jsxDesugaring = host.getCompilerOptions().jsx !== JsxEmit.Preserve; + let shouldEmitJsx = (s: SourceFile) => (s.languageVariant === LanguageVariant.JSX && !jsxDesugaring); + + if (targetSourceFile === undefined) { + forEach(host.getSourceFiles(), sourceFile => { + if (shouldEmitToOwnFile(sourceFile, compilerOptions)) { + let jsFilePath = getOwnEmitOutputFilePath(sourceFile, host, shouldEmitJsx(sourceFile) ? ".jsx" : ".js"); + printFile(jsFilePath, sourceFile); + } + }); + + if (compilerOptions.outFile || compilerOptions.out) { + printFile(compilerOptions.outFile || compilerOptions.out); + } + } + else { + // targetSourceFile is specified (e.g calling emitter from language service or calling getSemanticDiagnostic from language service) + if (shouldEmitToOwnFile(targetSourceFile, compilerOptions)) { + let jsFilePath = getOwnEmitOutputFilePath(targetSourceFile, host, shouldEmitJsx(targetSourceFile) ? ".jsx" : ".js"); + printFile(jsFilePath, targetSourceFile); + } + else if (!isDeclarationFile(targetSourceFile) && (compilerOptions.outFile || compilerOptions.out)) { + printFile(compilerOptions.outFile || compilerOptions.out); + } + } + + // Sort and make the unique list of diagnostics + diagnostics = sortAndDeduplicateDiagnostics(diagnostics); + + return { + emitSkipped: false, + diagnostics, + sourceMaps: sourceMapDataList + }; + + function printFile(jsFilePath: string, sourceFile?: SourceFile) { + let writer = createTextWriter(host.getNewLine()); + let nodes = sourceFile ? [sourceFile] : host.getSourceFiles(); + let transformationChain = getTransformationChain(compilerOptions); + let sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap + ? createSourceMapWriter(host, writer, jsFilePath, sourceFile) + : getNullSourceMapWriter(); + + // Print each file to the writer after executing the transformation chain. + printFiles(resolver, host, writer, filter(nodes, isNonDeclarationFile), transformationChain, sourceMap); + + // Write the output file. + writeFile(host, diagnostics, jsFilePath, writer.getText(), compilerOptions.emitBOM); + + // If source maps were requested, write the source map. + if (compilerOptions.sourceMap) { + writeFile(host, diagnostics, jsFilePath + ".map", sourceMap.getText(), /*writeByteOrderMark*/ false); + } + + // If declarations were requested, write the declaration file. + if (compilerOptions.declaration) { + writeDeclarationFile(jsFilePath, sourceFile, host, resolver, diagnostics); + } + + // Record source map data for the test harness. + if (sourceMapDataList) { + sourceMapDataList.push(sourceMap.sourceMapData); + } + } + } } diff --git a/src/compiler/factory.generated.ts b/src/compiler/factory.generated.ts index 96819bb316c..a7d47ce6450 100644 --- a/src/compiler/factory.generated.ts +++ b/src/compiler/factory.generated.ts @@ -1556,10 +1556,16 @@ namespace ts { export function isSourceFile(node: Node): node is SourceFile { return node && node.kind === SyntaxKind.SourceFile; } - export function isFunctionBody(node: Node): node is FunctionBody { + export function isLiteralExpression(node: Node): node is LiteralExpression { if (node) { switch (node.kind) { - case SyntaxKind.Block: + case SyntaxKind.NumericLiteral: + case SyntaxKind.RegularExpressionLiteral: + case SyntaxKind.NoSubstitutionTemplateLiteral: + case SyntaxKind.TemplateHead: + case SyntaxKind.TemplateMiddle: + case SyntaxKind.TemplateTail: + case SyntaxKind.StringLiteral: return true; } } @@ -1815,6 +1821,15 @@ namespace ts { } return false; } + export function isFunctionBody(node: Node): node is FunctionBody { + if (node) { + switch (node.kind) { + case SyntaxKind.Block: + return true; + } + } + return false; + } export function isUnaryExpression(node: Node): node is UnaryExpression { if (node) { switch (node.kind) { @@ -1856,21 +1871,6 @@ namespace ts { } return false; } - export function isLiteralExpression(node: Node): node is LiteralExpression { - if (node) { - switch (node.kind) { - case SyntaxKind.NumericLiteral: - case SyntaxKind.RegularExpressionLiteral: - case SyntaxKind.NoSubstitutionTemplateLiteral: - case SyntaxKind.TemplateHead: - case SyntaxKind.TemplateMiddle: - case SyntaxKind.TemplateTail: - case SyntaxKind.StringLiteral: - return true; - } - } - return false; - } export function isConciseBody(node: Node): node is ConciseBody { if (node) { switch (node.kind) { @@ -2068,6 +2068,37 @@ namespace ts { } return false; } + export function isJSDocTypeExpression(node: Node): node is JSDocTypeExpression { + if (node) { + switch (node.kind) { + case SyntaxKind.JSDocTypeExpression: + return true; + } + } + return false; + } + export function isJSDocRecordMember(node: Node): node is JSDocRecordMember { + if (node) { + switch (node.kind) { + case SyntaxKind.JSDocRecordMember: + return true; + } + } + return false; + } + export function isJSDocTag(node: Node): node is JSDocTag { + if (node) { + switch (node.kind) { + case SyntaxKind.JSDocTag: + case SyntaxKind.JSDocTemplateTag: + case SyntaxKind.JSDocReturnTag: + case SyntaxKind.JSDocTypeTag: + case SyntaxKind.JSDocParameterTag: + return true; + } + } + return false; + } export function acceptTransformer(transformer: Transformer, node: Node, visitor: (node: Node, write: (node: Node) => void) => void): Node { if (node) { switch (node.kind) { diff --git a/src/compiler/printer.ts b/src/compiler/printer.ts index 75401a3eac5..2c59cd9fa47 100644 --- a/src/compiler/printer.ts +++ b/src/compiler/printer.ts @@ -1,12 +1,13 @@ /// /// /// +/// /* @internal */ namespace ts { const delimiters = createDelimiterMap(); - export function printFiles(resolver: EmitResolver, host: EmitHost, sourceFiles: SourceFile[], transformations?: TransformationChain) { + export function printFiles(resolver: EmitResolver, host: EmitHost, writer: EmitTextWriter, sourceFiles: SourceFile[], transformations?: TransformationChain, sourceMap: SourceMapWriter = getNullSourceMapWriter()) { // emit output for the __extends helper function const extendsHelper = ` var __extends = (this && this.__extends) || function (d, b) { @@ -70,7 +71,6 @@ function __export(m) { } })`; - let writer = createTextWriter(host.getNewLine()); let { write, writeTextOfNode, @@ -80,6 +80,15 @@ function __export(m) { decreaseIndent } = writer; + let { + setSourceFile, + emitStart, + emitEnd, + emitPos, + pushScope, + popScope + } = sourceMap; + // Add the pretty printer to the transformation chain. transformations = transformations ? chainTransformationPhases([transformations, createPrinter]) : createPrinter; @@ -87,7 +96,11 @@ function __export(m) { transformFiles(resolver, host, sourceFiles, transformations); writeLine(); - return writer.getText(); + + let sourceMappingURL = sourceMap.getSourceMappingURL(); + if (sourceMappingURL) { + write(`//# sourceMappingURL=${sourceMappingURL}`); + } function createPrinter(transformer: Transformer) { let { @@ -120,20 +133,6 @@ function __export(m) { let emitTrailingComments = function (node: Node) { }; let emitTrailingCommentsOfPosition = function (pos: number) { }; - /** Called just before starting emit of a node */ - let emitStart = function (node: Node) { }; - - /** Called once the emit of the node is done */ - let emitEnd = function (node: Node) { }; - - /** Called to before starting the lexical scopes as in function/class in the emitted code because of node - * @param scopeDeclaration node that starts the lexical scope - * @param scopeName Optional name of this scope instead of deducing one from the declaration node */ - let scopeEmitStart = function(scopeDeclaration: Node, scopeName?: string) { }; - - /** Called after coming out of the scope */ - let scopeEmitEnd = function() { }; - if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) { // initializeEmitterWithSourceMaps(); } @@ -1179,7 +1178,7 @@ function __export(m) { else { emitStart(node); write("{"); - scopeEmitStart(getParentNode()); + pushScope(getParentNode()); if (node.flags & NodeFlags.SingleLine) { emitList(node, node.statements, addHelpers(node, ListFormat.SpaceBetweenBraces | ListFormat.SingleLine | format)); } @@ -1187,7 +1186,7 @@ function __export(m) { emitList(node, node.statements, addHelpers(node, ListFormat.Indented | ListFormat.MultiLine | format)); } - scopeEmitEnd(); + popScope(); write("}"); emitEnd(node); } @@ -1413,7 +1412,7 @@ function __export(m) { function emitBlockFunctionBody(parentNode: Node, body: Block) { let statements = body.statements; write(" {"); - scopeEmitStart(parentNode); + pushScope(parentNode); // Emit all the prologue directives (like "use strict"). increaseIndent(); @@ -1427,7 +1426,7 @@ function __export(m) { emitList(body, statements, addHelpers(body, ListFormat.MultiLine | ListFormat.Indented | ListFormat.LexicalEnvironment), statementOffset); } - scopeEmitEnd(); + popScope(); write("}"); } @@ -1449,9 +1448,9 @@ function __export(m) { tempFlags = 0; write(" {"); - scopeEmitStart(node); + pushScope(node); emitList(node, node.members, ListFormat.Indented | ListFormat.MultiLine); - scopeEmitEnd(); + popScope(); write("}"); tempFlags = savedTempFlags; @@ -1802,6 +1801,8 @@ function __export(m) { tempFlags = 0; currentSourceFile = node; + setSourceFile(node); + writeLine(); emitShebang(); emitDetachedComments(node); diff --git a/src/compiler/program.ts b/src/compiler/program.ts index 0264305d137..29d2920345f 100644 --- a/src/compiler/program.ts +++ b/src/compiler/program.ts @@ -95,7 +95,7 @@ namespace ts { jsonContent = jsonText ? <{ typings?: string }>JSON.parse(jsonText) : { typings: undefined }; } catch (e) { - // gracefully handle if readFile fails or returns not JSON + // gracefully handle if readFile fails or returns not JSON jsonContent = { typings: undefined }; } @@ -166,7 +166,7 @@ namespace ts { searchName = normalizePath(combinePaths(searchPath, moduleName)); referencedSourceFile = forEach(supportedExtensions, extension => { if (extension === ".tsx" && !compilerOptions.jsx) { - // resolve .tsx files only if jsx support is enabled + // resolve .tsx files only if jsx support is enabled // 'logical not' handles both undefined and None cases return undefined; } @@ -552,7 +552,11 @@ namespace ts { let start = new Date().getTime(); - let emitResult = emitFiles( + let emitter = options.experimentalTransforms + ? emitFilesUsingTransforms + : emitFiles; + + let emitResult = emitter( emitResolver, getEmitHost(writeFileCallback), sourceFile); @@ -711,13 +715,13 @@ namespace ts { case SyntaxKind.ModuleDeclaration: if ((node).name.kind === SyntaxKind.StringLiteral && (node.flags & NodeFlags.Ambient || isDeclarationFile(file))) { // TypeScript 1.0 spec (April 2014): 12.1.6 - // An AmbientExternalModuleDeclaration declares an external module. + // An AmbientExternalModuleDeclaration declares an external module. // This type of declaration is permitted only in the global module. // The StringLiteral must specify a top - level external module name. // Relative external module names are not permitted forEachChild((node).body, node => { // TypeScript 1.0 spec (April 2014): 12.1.6 - // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules + // An ExternalImportDeclaration in anAmbientExternalModuleDeclaration may reference other external modules // only through top - level external module names. Relative external module names are not permitted. collect(node, /* allowRelativeModuleNames */ false); }); @@ -780,7 +784,7 @@ namespace ts { if (filesByName.contains(normalizedAbsolutePath)) { const file = getSourceFileFromCache(normalizedAbsolutePath, /*useAbsolutePath*/ true); // we don't have resolution for this relative file name but the match was found by absolute file name - // store resolution for relative name as well + // store resolution for relative name as well filesByName.set(fileName, file); return file; } @@ -1071,7 +1075,7 @@ namespace ts { !options.experimentalDecorators) { programDiagnostics.add(createCompilerDiagnostic(Diagnostics.Option_0_cannot_be_specified_without_specifying_option_1, "emitDecoratorMetadata", "experimentalDecorators")); } - + if (FORCE_TRANSFORMS) { options.experimentalTransforms = true; } diff --git a/src/compiler/sourcemap.ts b/src/compiler/sourcemap.ts new file mode 100644 index 00000000000..26aaa0ab653 --- /dev/null +++ b/src/compiler/sourcemap.ts @@ -0,0 +1,404 @@ +/// +/// +/// + +/* @internal */ +namespace ts { + export interface SourceMapWriter { + sourceMapData?: SourceMapData; + setSourceFile(sourceFile: SourceFile): void; + emitPos(pos: number, skipTrivia?: boolean): void; + emitStart(range: TextRange): void; + emitEnd(range: TextRange): void; + pushScope(scopeDeclaration: Node, scopeName?: string): void; + popScope(): void; + getText(): string; + getSourceMappingURL(): string; + } + + let nop = <(...args: any[]) => any>Function.prototype; + let nullSourceMapWriter: SourceMapWriter; + + export function getNullSourceMapWriter(): SourceMapWriter { + if (nullSourceMapWriter === undefined) { + nullSourceMapWriter = { + setSourceFile: nop, + emitStart: nop, + emitEnd: nop, + emitPos: nop, + pushScope: nop, + popScope: nop, + getText: nop, + getSourceMappingURL: nop + }; + } + + return nullSourceMapWriter; + } + + export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter, filePath: string, root?: SourceFile): SourceMapWriter { + let compilerOptions = host.getCompilerOptions(); + let currentSourceFile: SourceFile; + let sourceMapDir: string; // The directory in which sourcemap will be + + // Current source map file and its index in the sources list + let sourceMapSourceIndex = -1; + + // Names and its index map + let sourceMapNameIndexMap: Map = {}; + let sourceMapNameIndices: number[] = []; + + // Last recorded and encoded spans + let lastRecordedSourceMapSpan: SourceMapSpan; + let lastEncodedSourceMapSpan: SourceMapSpan = { + emittedLine: 1, + emittedColumn: 1, + sourceLine: 1, + sourceColumn: 1, + sourceIndex: 0 + }; + let lastEncodedNameIndex = 0; + + // Initialize source map data + let sourceMapJsFile = getBaseFileName(normalizeSlashes(filePath)); + let sourceMapData: SourceMapData = { + sourceMapFilePath: filePath + ".map", + jsSourceMappingURL: sourceMapJsFile + ".map", + sourceMapFile: sourceMapJsFile, + sourceMapSourceRoot: compilerOptions.sourceRoot || "", + sourceMapSources: [], + inputSourceFileNames: [], + sourceMapNames: [], + sourceMapMappings: "", + sourceMapSourcesContent: compilerOptions.inlineSources ? [] : undefined, + sourceMapDecodedMappings: [] + }; + + // 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 = ts.normalizeSlashes(sourceMapData.sourceMapSourceRoot); + if (sourceMapData.sourceMapSourceRoot.length && sourceMapData.sourceMapSourceRoot.charCodeAt(sourceMapData.sourceMapSourceRoot.length - 1) !== CharacterCodes.slash) { + sourceMapData.sourceMapSourceRoot += directorySeparator; + } + + if (compilerOptions.mapRoot) { + sourceMapDir = normalizeSlashes(compilerOptions.mapRoot); + if (root) { // 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(root, 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)); + } + + return { + sourceMapData, + setSourceFile: recordNewSourceFileStart, + emitPos: recordSourceMapSpan, + emitStart: recordEmitNodeStartSpan, + emitEnd: recordEmitNodeEndSpan, + pushScope: recordScopeNameOfNode, + popScope: recordScopeNameEnd, + getText, + getSourceMappingURL, + }; + + function getSourceMapNameIndex() { + return sourceMapNameIndices.length ? lastOrUndefined(sourceMapNameIndices) : -1; + } + + // Encoding for sourcemap span + function encodeLastRecordedSourceMapSpan() { + if (!lastRecordedSourceMapSpan || lastRecordedSourceMapSpan === lastEncodedSourceMapSpan) { + return; + } + + let prevEncodedEmittedColumn = lastEncodedSourceMapSpan.emittedColumn; + // Line/Comma delimiters + if (lastEncodedSourceMapSpan.emittedLine === lastRecordedSourceMapSpan.emittedLine) { + // Emit comma to separate the entry + if (sourceMapData.sourceMapMappings) { + sourceMapData.sourceMapMappings += ","; + } + } + else { + // Emit line delimiters + for (let encodedLine = lastEncodedSourceMapSpan.emittedLine; encodedLine < lastRecordedSourceMapSpan.emittedLine; encodedLine++) { + sourceMapData.sourceMapMappings += ";"; + } + prevEncodedEmittedColumn = 1; + } + + // 1. Relative Column 0 based + sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.emittedColumn - prevEncodedEmittedColumn); + + // 2. Relative sourceIndex + sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceIndex - lastEncodedSourceMapSpan.sourceIndex); + + // 3. Relative sourceLine 0 based + sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceLine - lastEncodedSourceMapSpan.sourceLine); + + // 4. Relative sourceColumn 0 based + sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.sourceColumn - lastEncodedSourceMapSpan.sourceColumn); + + // 5. Relative namePosition 0 based + if (lastRecordedSourceMapSpan.nameIndex >= 0) { + sourceMapData.sourceMapMappings += base64VLQFormatEncode(lastRecordedSourceMapSpan.nameIndex - lastEncodedNameIndex); + lastEncodedNameIndex = lastRecordedSourceMapSpan.nameIndex; + } + + lastEncodedSourceMapSpan = lastRecordedSourceMapSpan; + sourceMapData.sourceMapDecodedMappings.push(lastEncodedSourceMapSpan); + } + + function recordSourceMapSpan(pos: number, skipTrivia?: boolean) { + if (pos === -1) { + return; + } + + if (skipTrivia) { + pos = ts.skipTrivia(currentSourceFile.text, pos); + } + + let sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos); + + // Convert the location to be one-based. + sourceLinePos.line++; + sourceLinePos.character++; + + let emittedLine = writer.getLine(); + let emittedColumn = writer.getColumn(); + + // If this location wasn't recorded or the location in source is going backwards, record the span + if (!lastRecordedSourceMapSpan || + lastRecordedSourceMapSpan.emittedLine !== emittedLine || + lastRecordedSourceMapSpan.emittedColumn !== emittedColumn || + (lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex && + (lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line || + (lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) { + + // Encode the last recordedSpan before assigning new + encodeLastRecordedSourceMapSpan(); + + // New span + lastRecordedSourceMapSpan = { + emittedLine: emittedLine, + emittedColumn: emittedColumn, + sourceLine: sourceLinePos.line, + sourceColumn: sourceLinePos.character, + nameIndex: getSourceMapNameIndex(), + sourceIndex: sourceMapSourceIndex + }; + } + else { + // Take the new pos instead since there is no change in emittedLine and column since last location + lastRecordedSourceMapSpan.sourceLine = sourceLinePos.line; + lastRecordedSourceMapSpan.sourceColumn = sourceLinePos.character; + lastRecordedSourceMapSpan.sourceIndex = sourceMapSourceIndex; + } + } + + function recordEmitNodeStartSpan(range: TextRange) { + recordSourceMapSpan(range.pos, /*skipTrivia*/ true); + } + + function recordEmitNodeEndSpan(range: TextRange) { + recordSourceMapSpan(range.end, /*skipTrivia*/ false); + } + + function recordNewSourceFileStart(sourceFile: SourceFile) { + currentSourceFile = getOriginalNodeIf(sourceFile, isSourceFile); + + // Add the file to tsFilePaths + // If sourceroot option: Use the relative path corresponding to the common directory path + // otherwise source locations relative to map file location + let sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir; + + let source = getRelativePathToDirectoryOrUrl(sourcesDirectoryPath, + currentSourceFile.fileName, + host.getCurrentDirectory(), + host.getCanonicalFileName, + /*isAbsolutePathAnUrl*/ true); + + sourceMapSourceIndex = indexOf(sourceMapData.sourceMapSources, source); + if (sourceMapSourceIndex === -1) { + sourceMapSourceIndex = sourceMapData.sourceMapSources.length; + sourceMapData.sourceMapSources.push(source); + + // The one that can be used from program to get the actual source file + sourceMapData.inputSourceFileNames.push(sourceFile.fileName); + + if (compilerOptions.inlineSources) { + sourceMapData.sourceMapSourcesContent.push(sourceFile.text); + } + } + } + + function recordScopeNameIndex(scopeNameIndex: number) { + sourceMapNameIndices.push(scopeNameIndex); + } + + function recordScopeNameStart(scopeDeclaration: Node, scopeName: string) { + let scopeNameIndex = -1; + if (scopeName) { + let parentIndex = getSourceMapNameIndex(); + if (parentIndex !== -1) { + // Child scopes are always shown with a dot (even if they have no name), + // unless it is a computed property. Then it is shown with brackets, + // but the brackets are included in the name. + let name = (scopeDeclaration).name; + if (!name || name.kind !== SyntaxKind.ComputedPropertyName) { + scopeName = "." + scopeName; + } + scopeName = sourceMapData.sourceMapNames[parentIndex] + scopeName; + } + + scopeNameIndex = getProperty(sourceMapNameIndexMap, scopeName); + if (scopeNameIndex === undefined) { + scopeNameIndex = sourceMapData.sourceMapNames.length; + sourceMapData.sourceMapNames.push(scopeName); + sourceMapNameIndexMap[scopeName] = scopeNameIndex; + } + } + recordScopeNameIndex(scopeNameIndex); + } + + function recordScopeNameOfNode(scopeDeclaration: Node, scopeName?: string) { + if (scopeName) { + // The scope was already given a name use it + recordScopeNameStart(scopeDeclaration, scopeName); + } + else if (scopeDeclaration.kind === SyntaxKind.FunctionDeclaration || + scopeDeclaration.kind === SyntaxKind.FunctionExpression || + scopeDeclaration.kind === SyntaxKind.MethodDeclaration || + scopeDeclaration.kind === SyntaxKind.MethodSignature || + scopeDeclaration.kind === SyntaxKind.GetAccessor || + scopeDeclaration.kind === SyntaxKind.SetAccessor || + scopeDeclaration.kind === SyntaxKind.ModuleDeclaration || + scopeDeclaration.kind === SyntaxKind.ClassDeclaration || + scopeDeclaration.kind === SyntaxKind.EnumDeclaration) { + // Declaration and has associated name use it + if ((scopeDeclaration).name) { + let name = (scopeDeclaration).name; + // For computed property names, the text will include the brackets + scopeName = name.kind === SyntaxKind.ComputedPropertyName + ? getTextOfNode(name) + : ((scopeDeclaration).name).text; + } + + recordScopeNameStart(scopeDeclaration, scopeName); + } + else { + // Block just use the name from upper level scope + recordScopeNameIndex(getSourceMapNameIndex()); + } + } + + function recordScopeNameEnd() { + sourceMapNameIndices.pop(); + } + + function getText() { + encodeLastRecordedSourceMapSpan(); + + return stringify({ + version: 3, + file: sourceMapData.sourceMapFile, + sourceRoot: sourceMapData.sourceMapSourceRoot, + sources: sourceMapData.sourceMapSources, + sourcesContent: sourceMapData.sourceMapSourcesContent, + names: sourceMapData.sourceMapNames, + mappings: sourceMapData.sourceMapMappings + }); + } + + function getSourceMappingURL() { + if (compilerOptions.inlineSourceMap) { + // Encode the sourceMap into the sourceMap url + let base64SourceMapText = convertToBase64(getText()); + return `data:application/json;base64,${base64SourceMapText}`; + } + else { + return sourceMapData.jsSourceMappingURL; + } + } + } + + const base64Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/"; + + function base64FormatEncode(inValue: number) { + if (inValue < 64) { + return base64Chars.charAt(inValue); + } + + throw TypeError(inValue + ": not a 64 based value"); + } + + function base64VLQFormatEncode(inValue: number) { + // Add a new least significant bit that has the sign of the value. + // if negative number the least significant bit that gets added to the number has value 1 + // else least significant bit value that gets added is 0 + // eg. -1 changes to binary : 01 [1] => 3 + // +1 changes to binary : 01 [0] => 2 + if (inValue < 0) { + inValue = ((-inValue) << 1) + 1; + } + else { + inValue = inValue << 1; + } + + // Encode 5 bits at a time starting from least significant bits + let encodedStr = ""; + do { + let currentDigit = inValue & 31; // 11111 + inValue = inValue >> 5; + if (inValue > 0) { + // There are still more digits to decode, set the msb (6th bit) + currentDigit = currentDigit | 32; + } + encodedStr = encodedStr + base64FormatEncode(currentDigit); + } while (inValue > 0); + + return encodedStr; + } + + var stringify = JSON && JSON.stringify ? JSON.stringify : function stringify(value: any): string { + return value == null ? "null" + : typeof value === "string" ? `"${escapeString(value)}"` + : typeof value === "number" ? String(value) + : typeof value === "boolean" ? value ? "true" : "false" + : hasToJson(value) ? stringify(value.toJSON()) + : isArray(value) ? `[${reduceLeft(value, stringifyElement, "")}]` + : typeof value === "object" ? `{${reduceProperties(value, stringifyProperty, "")}}` + : "null"; + } + + function hasToJson(value: any) { + return typeof value === "object" && typeof value.toJSON === "function"; + } + + function stringifyElement(memo: string, value: any) { + return (memo ? memo + "," : memo) + stringify(value); + } + + function stringifyProperty(memo: string, value: any, key: string) { + return value === undefined ? memo + : (memo ? memo + "," : memo) + `"${escapeString(key)}":${stringify(value)}`; + } +} \ No newline at end of file diff --git a/src/compiler/tsconfig.json b/src/compiler/tsconfig.json index 163910ac199..e06245c383f 100644 --- a/src/compiler/tsconfig.json +++ b/src/compiler/tsconfig.json @@ -26,6 +26,9 @@ "transforms/module/es6.ts", "transforms/jsx.ts", "transforms/es6.ts", + "declarationEmitter.ts", + "sourcemap.ts", + "printer.ts", "emitter.ts", "program.ts", "commandLineParser.ts",