Merge pull request #5780 from Microsoft/extractSourceMaps
Extract source map generation logic out of the emitter.
This commit is contained in:
commit
266600da08
|
@ -40,6 +40,7 @@ var compilerSources = [
|
|||
"utilities.ts",
|
||||
"binder.ts",
|
||||
"checker.ts",
|
||||
"sourcemap.ts",
|
||||
"declarationEmitter.ts",
|
||||
"emitter.ts",
|
||||
"program.ts",
|
||||
|
@ -59,6 +60,7 @@ var servicesSources = [
|
|||
"utilities.ts",
|
||||
"binder.ts",
|
||||
"checker.ts",
|
||||
"sourcemap.ts",
|
||||
"declarationEmitter.ts",
|
||||
"emitter.ts",
|
||||
"program.ts",
|
||||
|
@ -475,7 +477,7 @@ compileFile(servicesFile, servicesSources,[builtLocalDirectory, copyright].conca
|
|||
var nodeDefinitionsFileContents = definitionFileContents + "\r\nexport = ts;";
|
||||
fs.writeFileSync(nodeDefinitionsFile, nodeDefinitionsFileContents);
|
||||
|
||||
// Node package definition file to be distributed without the package. Created by replacing
|
||||
// Node package definition file to be distributed without the package. Created by replacing
|
||||
// 'ts' namespace with '"typescript"' as a module.
|
||||
var nodeStandaloneDefinitionsFileContents = definitionFileContents.replace(/declare (namespace|module) ts/g, 'declare module "typescript"');
|
||||
fs.writeFileSync(nodeStandaloneDefinitionsFile, nodeStandaloneDefinitionsFileContents);
|
||||
|
@ -884,7 +886,7 @@ var tslintRulesOutFiles = tslintRules.map(function(p) {
|
|||
desc("Compiles tslint rules to js");
|
||||
task("build-rules", tslintRulesOutFiles);
|
||||
tslintRulesFiles.forEach(function(ruleFile, i) {
|
||||
compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false, /*noOutFile*/ true, /*generateDeclarations*/ false, path.join(builtLocalDirectory, "tslint"));
|
||||
compileFile(tslintRulesOutFiles[i], [ruleFile], [ruleFile], [], /*useBuiltCompiler*/ false, /*noOutFile*/ true, /*generateDeclarations*/ false, path.join(builtLocalDirectory, "tslint"));
|
||||
});
|
||||
|
||||
function getLinterOptions() {
|
||||
|
@ -947,7 +949,7 @@ function lintWatchFile(filename) {
|
|||
if (event !== "change") {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
if (!lintSemaphores[filename]) {
|
||||
lintSemaphores[filename] = true;
|
||||
lintFileAsync(getLinterOptions(), filename, function(err, result) {
|
||||
|
|
|
@ -356,6 +356,33 @@ namespace ts {
|
|||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reduce the properties of a map.
|
||||
*
|
||||
* @param map The map to reduce
|
||||
* @param callback An aggregation function that is called for each entry in the map
|
||||
* @param initial The initial value for the reduction.
|
||||
*/
|
||||
export function reduceProperties<T, U>(map: Map<T>, callback: (aggregate: U, value: T, key: string) => U, initial: U): U {
|
||||
let result = initial;
|
||||
if (map) {
|
||||
for (const key in map) {
|
||||
if (hasProperty(map, key)) {
|
||||
result = callback(result, map[key], String(key));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests whether a value is an array.
|
||||
*/
|
||||
export function isArray(value: any): value is any[] {
|
||||
return Array.isArray ? Array.isArray(value) : value instanceof Array;
|
||||
}
|
||||
|
||||
export function memoize<T>(callback: () => T): () => T {
|
||||
let value: T;
|
||||
return () => {
|
||||
|
|
|
@ -1,4 +1,5 @@
|
|||
/// <reference path="checker.ts"/>
|
||||
/// <reference path="sourcemap.ts" />
|
||||
/// <reference path="declarationEmitter.ts"/>
|
||||
|
||||
/* @internal */
|
||||
|
@ -463,6 +464,9 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
const writer = createTextWriter(newLine);
|
||||
const { write, writeTextOfNode, writeLine, increaseIndent, decreaseIndent } = writer;
|
||||
|
||||
const sourceMap = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? createSourceMapWriter(host, writer) : getNullSourceMapWriter();
|
||||
const { setSourceFile, emitStart, emitEnd, emitPos, pushScope, popScope } = sourceMap;
|
||||
|
||||
let currentSourceFile: SourceFile;
|
||||
let currentText: string;
|
||||
let currentLineMap: number[];
|
||||
|
@ -497,38 +501,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
let exportEquals: ExportAssignment;
|
||||
let hasExportStars: boolean;
|
||||
|
||||
/** Write emitted output to disk */
|
||||
let writeEmittedFiles = writeJavaScriptFile;
|
||||
|
||||
let detachedCommentsInfo: { nodePos: number; detachedCommentEndPos: number }[];
|
||||
|
||||
let writeComment = writeCommentRange;
|
||||
|
||||
/** Emit a node */
|
||||
let emit = emitNodeWithCommentsAndWithoutSourcemap;
|
||||
|
||||
/** 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) { };
|
||||
|
||||
/** Emit the text for the given token that comes after startPos
|
||||
* This by default writes the text provided with the given tokenKind
|
||||
* but if optional emitFn callback is provided the text is emitted using the callback instead of default text
|
||||
* @param tokenKind the kind of the token to search and emit
|
||||
* @param startPos the position in the source to start searching for the token
|
||||
* @param emitFn if given will be invoked to emit the text instead of actual token emit */
|
||||
let emitToken = emitTokenText;
|
||||
|
||||
/** 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() { };
|
||||
|
||||
/** Sourcemap data that will get encoded */
|
||||
let sourceMapData: SourceMapData;
|
||||
|
||||
|
@ -554,18 +528,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
[ModuleKind.CommonJS]() {},
|
||||
};
|
||||
|
||||
|
||||
return doEmit;
|
||||
|
||||
function doEmit(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
|
||||
sourceMap.initialize(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
generatedNameSet = {};
|
||||
nodeToGeneratedName = [];
|
||||
isOwnFileEmit = !isBundledEmit;
|
||||
|
||||
if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
|
||||
initializeEmitterWithSourceMaps(jsFilePath, sourceMapFilePath, sourceFiles, isBundledEmit);
|
||||
}
|
||||
|
||||
// Emit helpers from all the files
|
||||
if (isBundledEmit && modulekind) {
|
||||
forEach(sourceFiles, emitEmitHelpers);
|
||||
|
@ -575,9 +545,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
forEach(sourceFiles, emitSourceFile);
|
||||
|
||||
writeLine();
|
||||
writeEmittedFiles(writer.getText(), jsFilePath, /*writeByteOrderMark*/ compilerOptions.emitBOM);
|
||||
|
||||
const sourceMappingURL = sourceMap.getSourceMappingURL();
|
||||
if (sourceMappingURL) {
|
||||
write(`//# sourceMappingURL=${sourceMappingURL}`);
|
||||
}
|
||||
|
||||
writeEmittedFiles(writer.getText(), jsFilePath, sourceMapFilePath, /*writeByteOrderMark*/ compilerOptions.emitBOM);
|
||||
|
||||
// reset the state
|
||||
sourceMap.reset();
|
||||
writer.reset();
|
||||
currentSourceFile = undefined;
|
||||
currentText = undefined;
|
||||
|
@ -616,7 +593,8 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
currentFileIdentifiers = sourceFile.identifiers;
|
||||
isCurrentFileExternalModule = isExternalModule(sourceFile);
|
||||
|
||||
emit(sourceFile);
|
||||
setSourceFile(sourceFile);
|
||||
emitNodeWithCommentsAndWithoutSourcemap(sourceFile);
|
||||
}
|
||||
|
||||
function isUniqueName(name: string): boolean {
|
||||
|
@ -713,399 +691,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
return nodeToGeneratedName[id] || (nodeToGeneratedName[id] = unescapeIdentifier(generateNameForNode(node)));
|
||||
}
|
||||
|
||||
function initializeEmitterWithSourceMaps(jsFilePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
|
||||
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
|
||||
const sourceMapNameIndexMap: Map<number> = {};
|
||||
const sourceMapNameIndices: number[] = [];
|
||||
function getSourceMapNameIndex() {
|
||||
return sourceMapNameIndices.length ? lastOrUndefined(sourceMapNameIndices) : -1;
|
||||
/** Write emitted output to disk */
|
||||
function writeEmittedFiles(emitOutput: string, jsFilePath: string, sourceMapFilePath: string, writeByteOrderMark: boolean) {
|
||||
if (compilerOptions.sourceMap && !compilerOptions.inlineSourceMap) {
|
||||
writeFile(host, emitterDiagnostics, sourceMapFilePath, sourceMap.getText(), /*writeByteOrderMark*/ false);
|
||||
}
|
||||
|
||||
// Last recorded and encoded spans
|
||||
let lastRecordedSourceMapSpan: SourceMapSpan;
|
||||
let lastEncodedSourceMapSpan: SourceMapSpan = {
|
||||
emittedLine: 1,
|
||||
emittedColumn: 1,
|
||||
sourceLine: 1,
|
||||
sourceColumn: 1,
|
||||
sourceIndex: 0
|
||||
};
|
||||
let lastEncodedNameIndex = 0;
|
||||
|
||||
// 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 base64VLQFormatEncode(inValue: number) {
|
||||
function base64FormatEncode(inValue: number) {
|
||||
if (inValue < 64) {
|
||||
return "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".charAt(inValue);
|
||||
}
|
||||
throw TypeError(inValue + ": not a 64 based value");
|
||||
}
|
||||
|
||||
// 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;
|
||||
}
|
||||
if (sourceMapDataList) {
|
||||
sourceMapDataList.push(sourceMap.getSourceMapData());
|
||||
}
|
||||
|
||||
function recordSourceMapSpan(pos: number) {
|
||||
const sourceLinePos = computeLineAndCharacterOfPosition(currentLineMap, pos);
|
||||
|
||||
// Convert the location to be one-based.
|
||||
sourceLinePos.line++;
|
||||
sourceLinePos.character++;
|
||||
|
||||
const emittedLine = writer.getLine();
|
||||
const emittedColumn = writer.getColumn();
|
||||
|
||||
// If this location wasn't recorded or the location in source is going backwards, record the span
|
||||
if (!lastRecordedSourceMapSpan ||
|
||||
lastRecordedSourceMapSpan.emittedLine !== emittedLine ||
|
||||
lastRecordedSourceMapSpan.emittedColumn !== emittedColumn ||
|
||||
(lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex &&
|
||||
(lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line ||
|
||||
(lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) {
|
||||
// Encode the last recordedSpan before assigning new
|
||||
encodeLastRecordedSourceMapSpan();
|
||||
|
||||
// New span
|
||||
lastRecordedSourceMapSpan = {
|
||||
emittedLine: 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(node: Node) {
|
||||
// Get the token pos after skipping to the token (ignoring the leading trivia)
|
||||
recordSourceMapSpan(skipTrivia(currentText, node.pos));
|
||||
}
|
||||
|
||||
function recordEmitNodeEndSpan(node: Node) {
|
||||
recordSourceMapSpan(node.end);
|
||||
}
|
||||
|
||||
function writeTextWithSpanRecord(tokenKind: SyntaxKind, startPos: number, emitFn?: () => void) {
|
||||
const tokenStartPos = ts.skipTrivia(currentText, startPos);
|
||||
recordSourceMapSpan(tokenStartPos);
|
||||
const tokenEndPos = emitTokenText(tokenKind, tokenStartPos, emitFn);
|
||||
recordSourceMapSpan(tokenEndPos);
|
||||
return tokenEndPos;
|
||||
}
|
||||
|
||||
function recordNewSourceFileStart(node: SourceFile) {
|
||||
// Add the file to tsFilePaths
|
||||
// If sourceroot option: Use the relative path corresponding to the common directory path
|
||||
// otherwise source locations relative to map file location
|
||||
const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
|
||||
|
||||
sourceMapData.sourceMapSources.push(getRelativePathToDirectoryOrUrl(sourcesDirectoryPath,
|
||||
node.fileName,
|
||||
host.getCurrentDirectory(),
|
||||
host.getCanonicalFileName,
|
||||
/*isAbsolutePathAnUrl*/ true));
|
||||
sourceMapSourceIndex = sourceMapData.sourceMapSources.length - 1;
|
||||
|
||||
// The one that can be used from program to get the actual source file
|
||||
sourceMapData.inputSourceFileNames.push(node.fileName);
|
||||
|
||||
if (compilerOptions.inlineSources) {
|
||||
if (!sourceMapData.sourceMapSourcesContent) {
|
||||
sourceMapData.sourceMapSourcesContent = [];
|
||||
}
|
||||
sourceMapData.sourceMapSourcesContent.push(node.text);
|
||||
}
|
||||
}
|
||||
|
||||
function recordScopeNameOfNode(node: Node, scopeName?: string) {
|
||||
function recordScopeNameIndex(scopeNameIndex: number) {
|
||||
sourceMapNameIndices.push(scopeNameIndex);
|
||||
}
|
||||
|
||||
function recordScopeNameStart(scopeName: string) {
|
||||
let scopeNameIndex = -1;
|
||||
if (scopeName) {
|
||||
const 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.
|
||||
const name = (<Declaration>node).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);
|
||||
}
|
||||
|
||||
if (scopeName) {
|
||||
// The scope was already given a name use it
|
||||
recordScopeNameStart(scopeName);
|
||||
}
|
||||
else if (node.kind === SyntaxKind.FunctionDeclaration ||
|
||||
node.kind === SyntaxKind.FunctionExpression ||
|
||||
node.kind === SyntaxKind.MethodDeclaration ||
|
||||
node.kind === SyntaxKind.MethodSignature ||
|
||||
node.kind === SyntaxKind.GetAccessor ||
|
||||
node.kind === SyntaxKind.SetAccessor ||
|
||||
node.kind === SyntaxKind.ModuleDeclaration ||
|
||||
node.kind === SyntaxKind.ClassDeclaration ||
|
||||
node.kind === SyntaxKind.EnumDeclaration) {
|
||||
// Declaration and has associated name use it
|
||||
if ((<Declaration>node).name) {
|
||||
const name = (<Declaration>node).name;
|
||||
// For computed property names, the text will include the brackets
|
||||
scopeName = name.kind === SyntaxKind.ComputedPropertyName
|
||||
? getTextOfNode(name)
|
||||
: (<Identifier>(<Declaration>node).name).text;
|
||||
}
|
||||
recordScopeNameStart(scopeName);
|
||||
}
|
||||
else {
|
||||
// Block just use the name from upper level scope
|
||||
recordScopeNameIndex(getSourceMapNameIndex());
|
||||
}
|
||||
}
|
||||
|
||||
function recordScopeNameEnd() {
|
||||
sourceMapNameIndices.pop();
|
||||
};
|
||||
|
||||
function writeCommentRangeWithMap(currentText: string, currentLineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) {
|
||||
recordSourceMapSpan(comment.pos);
|
||||
writeCommentRange(currentText, currentLineMap, writer, comment, newLine);
|
||||
recordSourceMapSpan(comment.end);
|
||||
}
|
||||
|
||||
function serializeSourceMapContents(version: number, file: string, sourceRoot: string, sources: string[], names: string[], mappings: string, sourcesContent?: string[]) {
|
||||
if (typeof JSON !== "undefined") {
|
||||
const map: any = {
|
||||
version,
|
||||
file,
|
||||
sourceRoot,
|
||||
sources,
|
||||
names,
|
||||
mappings
|
||||
};
|
||||
|
||||
if (sourcesContent !== undefined) {
|
||||
map.sourcesContent = sourcesContent;
|
||||
}
|
||||
|
||||
return JSON.stringify(map);
|
||||
}
|
||||
|
||||
return "{\"version\":" + version + ",\"file\":\"" + escapeString(file) + "\",\"sourceRoot\":\"" + escapeString(sourceRoot) + "\",\"sources\":[" + serializeStringArray(sources) + "],\"names\":[" + serializeStringArray(names) + "],\"mappings\":\"" + escapeString(mappings) + "\" " + (sourcesContent !== undefined ? ",\"sourcesContent\":[" + serializeStringArray(sourcesContent) + "]" : "") + "}";
|
||||
|
||||
function serializeStringArray(list: string[]): string {
|
||||
let output = "";
|
||||
for (let i = 0, n = list.length; i < n; i++) {
|
||||
if (i) {
|
||||
output += ",";
|
||||
}
|
||||
output += "\"" + escapeString(list[i]) + "\"";
|
||||
}
|
||||
return output;
|
||||
}
|
||||
}
|
||||
|
||||
function writeJavaScriptAndSourceMapFile(emitOutput: string, jsFilePath: string, writeByteOrderMark: boolean) {
|
||||
encodeLastRecordedSourceMapSpan();
|
||||
|
||||
const sourceMapText = serializeSourceMapContents(
|
||||
3,
|
||||
sourceMapData.sourceMapFile,
|
||||
sourceMapData.sourceMapSourceRoot,
|
||||
sourceMapData.sourceMapSources,
|
||||
sourceMapData.sourceMapNames,
|
||||
sourceMapData.sourceMapMappings,
|
||||
sourceMapData.sourceMapSourcesContent);
|
||||
|
||||
sourceMapDataList.push(sourceMapData);
|
||||
|
||||
let sourceMapUrl: string;
|
||||
if (compilerOptions.inlineSourceMap) {
|
||||
// Encode the sourceMap into the sourceMap url
|
||||
const base64SourceMapText = convertToBase64(sourceMapText);
|
||||
sourceMapData.jsSourceMappingURL = `data:application/json;base64,${base64SourceMapText}`;
|
||||
}
|
||||
else {
|
||||
// Write source map file
|
||||
writeFile(host, emitterDiagnostics, sourceMapData.sourceMapFilePath, sourceMapText, /*writeByteOrderMark*/ false);
|
||||
}
|
||||
sourceMapUrl = `//# sourceMappingURL=${sourceMapData.jsSourceMappingURL}`;
|
||||
|
||||
// Write sourcemap url to the js file and write the js file
|
||||
writeJavaScriptFile(emitOutput + sourceMapUrl, jsFilePath, writeByteOrderMark);
|
||||
}
|
||||
|
||||
// Initialize source map data
|
||||
sourceMapData = {
|
||||
sourceMapFilePath: sourceMapFilePath,
|
||||
jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined,
|
||||
sourceMapFile: getBaseFileName(normalizeSlashes(jsFilePath)),
|
||||
sourceMapSourceRoot: compilerOptions.sourceRoot || "",
|
||||
sourceMapSources: [],
|
||||
inputSourceFileNames: [],
|
||||
sourceMapNames: [],
|
||||
sourceMapMappings: "",
|
||||
sourceMapSourcesContent: 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 (!isBundledEmit) { // emitting single module file
|
||||
Debug.assert(sourceFiles.length === 1);
|
||||
// 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(sourceFiles[0], 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(jsFilePath)), // 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(jsFilePath));
|
||||
}
|
||||
|
||||
function emitNodeWithSourceMap(node: Node) {
|
||||
if (node) {
|
||||
if (nodeIsSynthesized(node)) {
|
||||
return emitNodeWithoutSourceMap(node);
|
||||
}
|
||||
if (node.kind !== SyntaxKind.SourceFile) {
|
||||
recordEmitNodeStartSpan(node);
|
||||
emitNodeWithoutSourceMap(node);
|
||||
recordEmitNodeEndSpan(node);
|
||||
}
|
||||
else {
|
||||
recordNewSourceFileStart(<SourceFile>node);
|
||||
emitNodeWithoutSourceMap(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function emitNodeWithCommentsAndWithSourcemap(node: Node) {
|
||||
emitNodeConsideringCommentsOption(node, emitNodeWithSourceMap);
|
||||
}
|
||||
|
||||
writeEmittedFiles = writeJavaScriptAndSourceMapFile;
|
||||
emit = emitNodeWithCommentsAndWithSourcemap;
|
||||
emitStart = recordEmitNodeStartSpan;
|
||||
emitEnd = recordEmitNodeEndSpan;
|
||||
emitToken = writeTextWithSpanRecord;
|
||||
scopeEmitStart = recordScopeNameOfNode;
|
||||
scopeEmitEnd = recordScopeNameEnd;
|
||||
writeComment = writeCommentRangeWithMap;
|
||||
}
|
||||
|
||||
function writeJavaScriptFile(emitOutput: string, jsFilePath: string, writeByteOrderMark: boolean) {
|
||||
writeFile(host, emitterDiagnostics, jsFilePath, emitOutput, writeByteOrderMark);
|
||||
}
|
||||
|
||||
|
@ -1144,7 +739,16 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
}
|
||||
}
|
||||
|
||||
function emitTokenText(tokenKind: SyntaxKind, startPos: number, emitFn?: () => void) {
|
||||
/** Emit the text for the given token that comes after startPos
|
||||
* This by default writes the text provided with the given tokenKind
|
||||
* but if optional emitFn callback is provided the text is emitted using the callback instead of default text
|
||||
* @param tokenKind the kind of the token to search and emit
|
||||
* @param startPos the position in the source to start searching for the token
|
||||
* @param emitFn if given will be invoked to emit the text instead of actual token emit */
|
||||
function emitToken(tokenKind: SyntaxKind, startPos: number, emitFn?: () => void) {
|
||||
const tokenStartPos = skipTrivia(currentText, startPos);
|
||||
emitPos(tokenStartPos);
|
||||
|
||||
const tokenString = tokenToString(tokenKind);
|
||||
if (emitFn) {
|
||||
emitFn();
|
||||
|
@ -1152,7 +756,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
else {
|
||||
write(tokenString);
|
||||
}
|
||||
return startPos + tokenString.length;
|
||||
|
||||
const tokenEndPos = tokenStartPos + tokenString.length;
|
||||
emitPos(tokenEndPos);
|
||||
return tokenEndPos;
|
||||
}
|
||||
|
||||
function emitOptional(prefix: string, node: Node) {
|
||||
|
@ -3093,7 +2700,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
|
||||
emitToken(SyntaxKind.OpenBraceToken, node.pos);
|
||||
increaseIndent();
|
||||
scopeEmitStart(node.parent);
|
||||
pushScope(node.parent);
|
||||
if (node.kind === SyntaxKind.ModuleBlock) {
|
||||
Debug.assert(node.parent.kind === SyntaxKind.ModuleDeclaration);
|
||||
emitCaptureThisForNodeIfNecessary(node.parent);
|
||||
|
@ -3105,7 +2712,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
decreaseIndent();
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.statements.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
}
|
||||
|
||||
function emitEmbeddedStatement(node: Node) {
|
||||
|
@ -4971,7 +4578,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
|
||||
function emitDownLevelExpressionFunctionBody(node: FunctionLikeDeclaration, body: Expression) {
|
||||
write(" {");
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
|
||||
increaseIndent();
|
||||
const outPos = writer.getTextPos();
|
||||
|
@ -5012,12 +4619,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
write("}");
|
||||
emitEnd(node.body);
|
||||
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
}
|
||||
|
||||
function emitBlockFunctionBody(node: FunctionLikeDeclaration, body: Block) {
|
||||
write(" {");
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
|
||||
const initialTextPos = writer.getTextPos();
|
||||
|
||||
|
@ -5052,7 +4659,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
}
|
||||
|
||||
emitToken(SyntaxKind.CloseBraceToken, body.statements.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
}
|
||||
|
||||
function findInitialSuperCall(ctor: ConstructorDeclaration): ExpressionStatement {
|
||||
|
@ -5338,7 +4945,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
let startIndex = 0;
|
||||
|
||||
write(" {");
|
||||
scopeEmitStart(node, "constructor");
|
||||
pushScope(node, "constructor");
|
||||
increaseIndent();
|
||||
if (ctor) {
|
||||
// Emit all the directive prologues (like "use strict"). These have to come before
|
||||
|
@ -5388,7 +4995,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
}
|
||||
decreaseIndent();
|
||||
emitToken(SyntaxKind.CloseBraceToken, ctor ? (<Block>ctor.body).statements.end : node.members.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
emitEnd(<Node>ctor || node);
|
||||
if (ctor) {
|
||||
emitTrailingComments(ctor);
|
||||
|
@ -5525,14 +5132,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
|
||||
write(" {");
|
||||
increaseIndent();
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
writeLine();
|
||||
emitConstructor(node, baseTypeNode);
|
||||
emitMemberFunctionsForES6AndHigher(node);
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.members.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
|
||||
// TODO(rbuckton): Need to go back to `let _a = class C {}` approach, removing the defineProperty call for now.
|
||||
|
||||
|
@ -5634,7 +5241,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
tempParameters = undefined;
|
||||
computedPropertyNamesToGeneratedNames = undefined;
|
||||
increaseIndent();
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
if (baseTypeNode) {
|
||||
writeLine();
|
||||
emitStart(baseTypeNode);
|
||||
|
@ -5667,7 +5274,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
decreaseIndent();
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.members.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
emitStart(node);
|
||||
write(")(");
|
||||
if (baseTypeNode) {
|
||||
|
@ -6228,12 +5835,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
emitEnd(node.name);
|
||||
write(") {");
|
||||
increaseIndent();
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
emitLines(node.members);
|
||||
decreaseIndent();
|
||||
writeLine();
|
||||
emitToken(SyntaxKind.CloseBraceToken, node.members.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
write(")(");
|
||||
emitModuleMemberName(node);
|
||||
write(" || (");
|
||||
|
@ -6357,7 +5964,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
else {
|
||||
write("{");
|
||||
increaseIndent();
|
||||
scopeEmitStart(node);
|
||||
pushScope(node);
|
||||
emitCaptureThisForNodeIfNecessary(node);
|
||||
writeLine();
|
||||
emit(node.body);
|
||||
|
@ -6365,7 +5972,7 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
writeLine();
|
||||
const moduleBlock = <ModuleBlock>getInnerMostModuleDeclarationFromDottedModule(node).body;
|
||||
emitToken(SyntaxKind.CloseBraceToken, moduleBlock.statements.end);
|
||||
scopeEmitEnd();
|
||||
popScope();
|
||||
}
|
||||
write(")(");
|
||||
// write moduleDecl = containingModule.m only if it is not exported es6 module member
|
||||
|
@ -7790,6 +7397,10 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
emitLeadingComments(node.endOfFileToken);
|
||||
}
|
||||
|
||||
function emit(node: Node): void {
|
||||
emitNodeConsideringCommentsOption(node, emitNodeWithSourceMap);
|
||||
}
|
||||
|
||||
function emitNodeWithCommentsAndWithoutSourcemap(node: Node): void {
|
||||
emitNodeConsideringCommentsOption(node, emitNodeWithoutSourceMap);
|
||||
}
|
||||
|
@ -7818,6 +7429,14 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
}
|
||||
}
|
||||
|
||||
function emitNodeWithSourceMap(node: Node): void {
|
||||
if (node) {
|
||||
emitStart(node);
|
||||
emitNodeWithoutSourceMap(node);
|
||||
emitEnd(node);
|
||||
}
|
||||
}
|
||||
|
||||
function emitNodeWithoutSourceMap(node: Node): void {
|
||||
if (node) {
|
||||
emitJavaScriptWorker(node);
|
||||
|
@ -8210,6 +7829,12 @@ var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, Promi
|
|||
}
|
||||
}
|
||||
|
||||
function writeComment(text: string, lineMap: number[], writer: EmitTextWriter, comment: CommentRange, newLine: string) {
|
||||
emitPos(comment.pos);
|
||||
writeCommentRange(text, lineMap, writer, comment, newLine);
|
||||
emitPos(comment.end);
|
||||
}
|
||||
|
||||
function emitShebang() {
|
||||
const shebang = getShebang(currentText);
|
||||
if (shebang) {
|
||||
|
|
|
@ -425,6 +425,12 @@ namespace ts {
|
|||
|
||||
/* @internal */
|
||||
export function skipTrivia(text: string, pos: number, stopAfterLineBreak?: boolean): number {
|
||||
// Using ! with a greater than test is a fast way of testing the following conditions:
|
||||
// pos === undefined || pos === null || isNaN(pos) || pos < 0;
|
||||
if (!(pos >= 0)) {
|
||||
return pos;
|
||||
}
|
||||
|
||||
// Keep in sync with couldStartTrivia
|
||||
while (true) {
|
||||
const ch = text.charCodeAt(pos);
|
||||
|
@ -567,12 +573,12 @@ namespace ts {
|
|||
}
|
||||
|
||||
/**
|
||||
* Extract comments from text prefixing the token closest following `pos`.
|
||||
* Extract comments from text prefixing the token closest following `pos`.
|
||||
* The return value is an array containing a TextRange for each comment.
|
||||
* Single-line comment ranges include the beginning '//' characters but not the ending line break.
|
||||
* Multi - line comment ranges include the beginning '/* and ending '<asterisk>/' characters.
|
||||
* The return value is undefined if no comments were found.
|
||||
* @param trailing
|
||||
* @param trailing
|
||||
* If false, whitespace is skipped until the first line break and comments between that location
|
||||
* and the next token are returned.
|
||||
* If true, comments occurring between the given position and the next line break are returned.
|
||||
|
|
416
src/compiler/sourcemap.ts
Normal file
416
src/compiler/sourcemap.ts
Normal file
|
@ -0,0 +1,416 @@
|
|||
/// <reference path="checker.ts"/>
|
||||
|
||||
/* @internal */
|
||||
namespace ts {
|
||||
export interface SourceMapWriter {
|
||||
getSourceMapData(): SourceMapData;
|
||||
setSourceFile(sourceFile: SourceFile): void;
|
||||
emitPos(pos: number): void;
|
||||
emitStart(range: TextRange): void;
|
||||
emitEnd(range: TextRange): void;
|
||||
pushScope(scopeDeclaration: Node, scopeName?: string): void;
|
||||
popScope(): void;
|
||||
getText(): string;
|
||||
getSourceMappingURL(): string;
|
||||
initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void;
|
||||
reset(): void;
|
||||
}
|
||||
|
||||
const nop = <(...args: any[]) => any>Function.prototype;
|
||||
let nullSourceMapWriter: SourceMapWriter;
|
||||
|
||||
export function getNullSourceMapWriter(): SourceMapWriter {
|
||||
if (nullSourceMapWriter === undefined) {
|
||||
nullSourceMapWriter = {
|
||||
getSourceMapData(): SourceMapData { return undefined; },
|
||||
setSourceFile(sourceFile: SourceFile): void { },
|
||||
emitStart(range: TextRange): void { },
|
||||
emitEnd(range: TextRange): void { },
|
||||
emitPos(pos: number): void { },
|
||||
pushScope(scopeDeclaration: Node, scopeName?: string): void { },
|
||||
popScope(): void { },
|
||||
getText(): string { return undefined; },
|
||||
getSourceMappingURL(): string { return undefined; },
|
||||
initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean): void { },
|
||||
reset(): void { },
|
||||
};
|
||||
}
|
||||
|
||||
return nullSourceMapWriter;
|
||||
}
|
||||
|
||||
export function createSourceMapWriter(host: EmitHost, writer: EmitTextWriter): SourceMapWriter {
|
||||
const 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: number;
|
||||
|
||||
// Names and its index map
|
||||
let sourceMapNameIndexMap: Map<number>;
|
||||
let sourceMapNameIndices: number[];
|
||||
|
||||
// Last recorded and encoded spans
|
||||
let lastRecordedSourceMapSpan: SourceMapSpan;
|
||||
let lastEncodedSourceMapSpan: SourceMapSpan;
|
||||
let lastEncodedNameIndex: number;
|
||||
|
||||
// Source map data
|
||||
let sourceMapData: SourceMapData;
|
||||
|
||||
return {
|
||||
getSourceMapData: () => sourceMapData,
|
||||
setSourceFile,
|
||||
emitPos,
|
||||
emitStart,
|
||||
emitEnd,
|
||||
pushScope,
|
||||
popScope,
|
||||
getText,
|
||||
getSourceMappingURL,
|
||||
initialize,
|
||||
reset,
|
||||
};
|
||||
|
||||
function initialize(filePath: string, sourceMapFilePath: string, sourceFiles: SourceFile[], isBundledEmit: boolean) {
|
||||
if (sourceMapData) {
|
||||
reset();
|
||||
}
|
||||
|
||||
currentSourceFile = undefined;
|
||||
|
||||
// Current source map file and its index in the sources list
|
||||
sourceMapSourceIndex = -1;
|
||||
|
||||
// Names and its index map
|
||||
sourceMapNameIndexMap = {};
|
||||
sourceMapNameIndices = [];
|
||||
|
||||
// Last recorded and encoded spans
|
||||
lastRecordedSourceMapSpan = undefined;
|
||||
lastEncodedSourceMapSpan = {
|
||||
emittedLine: 1,
|
||||
emittedColumn: 1,
|
||||
sourceLine: 1,
|
||||
sourceColumn: 1,
|
||||
sourceIndex: 0
|
||||
};
|
||||
lastEncodedNameIndex = 0;
|
||||
|
||||
// Initialize source map data
|
||||
sourceMapData = {
|
||||
sourceMapFilePath: sourceMapFilePath,
|
||||
jsSourceMappingURL: !compilerOptions.inlineSourceMap ? getBaseFileName(normalizeSlashes(sourceMapFilePath)) : undefined,
|
||||
sourceMapFile: getBaseFileName(normalizeSlashes(filePath)),
|
||||
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 (!isBundledEmit) { // emitting single module file
|
||||
Debug.assert(sourceFiles.length === 1);
|
||||
// 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(sourceFiles[0], 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));
|
||||
}
|
||||
}
|
||||
|
||||
function reset() {
|
||||
currentSourceFile = undefined;
|
||||
sourceMapDir = undefined;
|
||||
sourceMapSourceIndex = undefined;
|
||||
sourceMapNameIndexMap = undefined;
|
||||
sourceMapNameIndices = undefined;
|
||||
lastRecordedSourceMapSpan = undefined;
|
||||
lastEncodedSourceMapSpan = undefined;
|
||||
lastEncodedNameIndex = undefined;
|
||||
sourceMapData = undefined;
|
||||
}
|
||||
|
||||
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 emitPos(pos: number) {
|
||||
if (pos === -1) {
|
||||
return;
|
||||
}
|
||||
|
||||
const sourceLinePos = getLineAndCharacterOfPosition(currentSourceFile, pos);
|
||||
|
||||
// Convert the location to be one-based.
|
||||
sourceLinePos.line++;
|
||||
sourceLinePos.character++;
|
||||
|
||||
const emittedLine = writer.getLine();
|
||||
const emittedColumn = writer.getColumn();
|
||||
|
||||
// If this location wasn't recorded or the location in source is going backwards, record the span
|
||||
if (!lastRecordedSourceMapSpan ||
|
||||
lastRecordedSourceMapSpan.emittedLine !== emittedLine ||
|
||||
lastRecordedSourceMapSpan.emittedColumn !== emittedColumn ||
|
||||
(lastRecordedSourceMapSpan.sourceIndex === sourceMapSourceIndex &&
|
||||
(lastRecordedSourceMapSpan.sourceLine > sourceLinePos.line ||
|
||||
(lastRecordedSourceMapSpan.sourceLine === sourceLinePos.line && lastRecordedSourceMapSpan.sourceColumn > sourceLinePos.character)))) {
|
||||
|
||||
// Encode the last recordedSpan before assigning new
|
||||
encodeLastRecordedSourceMapSpan();
|
||||
|
||||
// New span
|
||||
lastRecordedSourceMapSpan = {
|
||||
emittedLine: 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 emitStart(range: TextRange) {
|
||||
emitPos(range.pos !== -1 ? skipTrivia(currentSourceFile.text, range.pos) : -1);
|
||||
}
|
||||
|
||||
function emitEnd(range: TextRange) {
|
||||
emitPos(range.end);
|
||||
}
|
||||
|
||||
function setSourceFile(sourceFile: SourceFile) {
|
||||
currentSourceFile = sourceFile;
|
||||
|
||||
// Add the file to tsFilePaths
|
||||
// If sourceroot option: Use the relative path corresponding to the common directory path
|
||||
// otherwise source locations relative to map file location
|
||||
const sourcesDirectoryPath = compilerOptions.sourceRoot ? host.getCommonSourceDirectory() : sourceMapDir;
|
||||
|
||||
const 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) {
|
||||
const 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.
|
||||
const name = (<Declaration>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 pushScope(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 ((<Declaration>scopeDeclaration).name) {
|
||||
const name = (<Declaration>scopeDeclaration).name;
|
||||
// For computed property names, the text will include the brackets
|
||||
scopeName = name.kind === SyntaxKind.ComputedPropertyName
|
||||
? getTextOfNode(name)
|
||||
: (<Identifier>(<Declaration>scopeDeclaration).name).text;
|
||||
}
|
||||
|
||||
recordScopeNameStart(scopeDeclaration, scopeName);
|
||||
}
|
||||
else {
|
||||
// Block just use the name from upper level scope
|
||||
recordScopeNameIndex(getSourceMapNameIndex());
|
||||
}
|
||||
}
|
||||
|
||||
function popScope() {
|
||||
sourceMapNameIndices.pop();
|
||||
}
|
||||
|
||||
function getText() {
|
||||
encodeLastRecordedSourceMapSpan();
|
||||
|
||||
return stringify({
|
||||
version: 3,
|
||||
file: sourceMapData.sourceMapFile,
|
||||
sourceRoot: sourceMapData.sourceMapSourceRoot,
|
||||
sources: sourceMapData.sourceMapSources,
|
||||
names: sourceMapData.sourceMapNames,
|
||||
mappings: sourceMapData.sourceMapMappings,
|
||||
sourcesContent: sourceMapData.sourceMapSourcesContent,
|
||||
});
|
||||
}
|
||||
|
||||
function getSourceMappingURL() {
|
||||
if (compilerOptions.inlineSourceMap) {
|
||||
// Encode the sourceMap into the sourceMap url
|
||||
const base64SourceMapText = convertToBase64(getText());
|
||||
return sourceMapData.jsSourceMappingURL = `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;
|
||||
}
|
||||
}
|
|
@ -2411,6 +2411,55 @@ namespace ts {
|
|||
return output;
|
||||
}
|
||||
|
||||
/**
|
||||
* Serialize an object graph into a JSON string. This is intended only for use on an acyclic graph
|
||||
* as the fallback implementation does not check for circular references by default.
|
||||
*/
|
||||
export const stringify: (value: any) => string = JSON && JSON.stringify
|
||||
? JSON.stringify
|
||||
: stringifyFallback;
|
||||
|
||||
/**
|
||||
* Serialize an object graph into a JSON string.
|
||||
*/
|
||||
function stringifyFallback(value: any): string {
|
||||
// JSON.stringify returns `undefined` here, instead of the string "undefined".
|
||||
return value === undefined ? undefined : stringifyValue(value);
|
||||
}
|
||||
|
||||
function stringifyValue(value: any): string {
|
||||
return typeof value === "string" ? `"${escapeString(value)}"`
|
||||
: typeof value === "number" ? isFinite(value) ? String(value) : "null"
|
||||
: typeof value === "boolean" ? value ? "true" : "false"
|
||||
: typeof value === "object" && value ? isArray(value) ? cycleCheck(stringifyArray, value) : cycleCheck(stringifyObject, value)
|
||||
: /*fallback*/ "null";
|
||||
}
|
||||
|
||||
function cycleCheck(cb: (value: any) => string, value: any) {
|
||||
Debug.assert(!value.hasOwnProperty("__cycle"), "Converting circular structure to JSON");
|
||||
value.__cycle = true;
|
||||
const result = cb(value);
|
||||
delete value.__cycle;
|
||||
return result;
|
||||
}
|
||||
|
||||
function stringifyArray(value: any) {
|
||||
return `[${reduceLeft(value, stringifyElement, "")}]`;
|
||||
}
|
||||
|
||||
function stringifyElement(memo: string, value: any) {
|
||||
return (memo ? memo + "," : memo) + stringifyValue(value);
|
||||
}
|
||||
|
||||
function stringifyObject(value: any) {
|
||||
return `{${reduceProperties(value, stringifyProperty, "")}}`;
|
||||
}
|
||||
|
||||
function stringifyProperty(memo: string, value: any, key: string) {
|
||||
return value === undefined || typeof value === "function" || key === "__cycle" ? memo
|
||||
: (memo ? memo + "," : memo) + `"${escapeString(key)}":${stringifyValue(value)}`;
|
||||
}
|
||||
|
||||
const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";
|
||||
|
||||
/**
|
||||
|
|
Loading…
Reference in a new issue