Skip to content

Inline source maps #2484

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 7 commits into from
Apr 27, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion Jakefile
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,8 @@ var harnessSources = [
"services/documentRegistry.ts",
"services/preProcessFile.ts",
"services/patternMatcher.ts",
"versionCache.ts"
"versionCache.ts",
"convertToBase64.ts"
].map(function (f) {
return path.join(unittestsDirectory, f);
})).concat([
Expand Down
8 changes: 8 additions & 0 deletions src/compiler/commandLineParser.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,14 @@ module ts {
type: "boolean",
description: Diagnostics.Print_this_message,
},
{
name: "inlineSourceMap",
type: "boolean",
},
{
name: "inlineSources",
type: "boolean",
},
{
name: "listFiles",
type: "boolean",
Expand Down
4 changes: 4 additions & 0 deletions src/compiler/diagnosticInformationMap.generated.ts
Original file line number Diff line number Diff line change
Expand Up @@ -454,6 +454,10 @@ module ts {
Option_noEmitOnError_cannot_be_specified_with_option_separateCompilation: { code: 5045, category: DiagnosticCategory.Error, key: "Option 'noEmitOnError' cannot be specified with option 'separateCompilation'." },
Option_out_cannot_be_specified_with_option_separateCompilation: { code: 5046, category: DiagnosticCategory.Error, key: "Option 'out' cannot be specified with option 'separateCompilation'." },
Option_separateCompilation_can_only_be_used_when_either_option_module_is_provided_or_option_target_is_ES6_or_higher: { code: 5047, category: DiagnosticCategory.Error, key: "Option 'separateCompilation' can only be used when either option'--module' is provided or option 'target' is 'ES6' or higher." },
Option_sourceMap_cannot_be_specified_with_option_inlineSourceMap: { code: 5048, category: DiagnosticCategory.Error, key: "Option 'sourceMap' cannot be specified with option 'inlineSourceMap'." },
Option_sourceRoot_cannot_be_specified_with_option_inlineSourceMap: { code: 5049, category: DiagnosticCategory.Error, key: "Option 'sourceRoot' cannot be specified with option 'inlineSourceMap'." },
Option_mapRoot_cannot_be_specified_with_option_inlineSourceMap: { code: 5050, category: DiagnosticCategory.Error, key: "Option 'mapRoot' cannot be specified with option 'inlineSourceMap'." },
Option_inlineSources_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided: { code: 5051, category: DiagnosticCategory.Error, key: "Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided." },
Concatenate_and_emit_output_to_single_file: { code: 6001, category: DiagnosticCategory.Message, key: "Concatenate and emit output to single file." },
Generates_corresponding_d_ts_file: { code: 6002, category: DiagnosticCategory.Message, key: "Generates corresponding '.d.ts' file." },
Specifies_the_location_where_debugger_should_locate_map_files_instead_of_generated_locations: { code: 6003, category: DiagnosticCategory.Message, key: "Specifies the location where debugger should locate map files instead of generated locations." },
Expand Down
17 changes: 17 additions & 0 deletions src/compiler/diagnosticMessages.json
Original file line number Diff line number Diff line change
Expand Up @@ -1804,6 +1804,23 @@
"category": "Error",
"code": 5047
},
"Option 'sourceMap' cannot be specified with option 'inlineSourceMap'.": {
"category": "Error",
"code": 5048
},
"Option 'sourceRoot' cannot be specified with option 'inlineSourceMap'.": {
"category": "Error",
"code": 5049
},
"Option 'mapRoot' cannot be specified with option 'inlineSourceMap'.": {
"category": "Error",
"code": 5050
},
"Option 'inlineSources' can only be used when either option '--inlineSourceMap' or option '--sourceMap' is provided.": {
"category": "Error",
"code": 5051
},

"Concatenate and emit output to single file.": {
"category": "Message",
"code": 6001
Expand Down
62 changes: 45 additions & 17 deletions src/compiler/emitter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {

let compilerOptions = host.getCompilerOptions();
let languageVersion = compilerOptions.target || ScriptTarget.ES3;
let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap ? [] : undefined;
let sourceMapDataList: SourceMapData[] = compilerOptions.sourceMap || compilerOptions.inlineSourceMap ? [] : undefined;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we put this on two more lines and maybe put parens around the || expression?

let diagnostics: Diagnostic[] = [];
let newLine = host.getNewLine();

Expand Down Expand Up @@ -181,7 +181,7 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {
/** Sourcemap data that will get encoded */
let sourceMapData: SourceMapData;

if (compilerOptions.sourceMap) {
if (compilerOptions.sourceMap || compilerOptions.inlineSourceMap) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe factor this out into a variable and use it above as well.

initializeEmitterWithSourceMaps();
}

Expand Down Expand Up @@ -506,6 +506,13 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {

// 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) {
Expand Down Expand Up @@ -577,19 +584,25 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {
recordSourceMapSpan(comment.end);
}

function serializeSourceMapContents(version: number, file: string, sourceRoot: string, sources: string[], names: string[], mappings: string) {
function serializeSourceMapContents(version: number, file: string, sourceRoot: string, sources: string[], names: string[], mappings: string, sourcesContent?: string[]) {
if (typeof JSON !== "undefined") {
return JSON.stringify({
version: version,
file: file,
sourceRoot: sourceRoot,
sources: sources,
names: names,
mappings: mappings
});
let 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) + "\"}";
return "{\"version\":" + version + ",\"file\":\"" + escapeString(file) + "\",\"sourceRoot\":\"" + escapeString(sourceRoot) + "\",\"sources\":[" + serializeStringArray(sources) + "],\"names\":[" + serializeStringArray(names) + "],\"mappings\":\"" + escapeString(mappings) + "\" " + (sourcesContent !== undefined ? ",\"sourcesContent\":[" + serializeStringArray(sourcesContent) + "]" : "") + "}";
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Template?

Also, factor out the last part into a variable sourcesContentJSON


function serializeStringArray(list: string[]): string {
let output = "";
Expand All @@ -604,19 +617,33 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {
}

function writeJavaScriptAndSourceMapFile(emitOutput: string, writeByteOrderMark: boolean) {
// Write source map file
encodeLastRecordedSourceMapSpan();
writeFile(host, diagnostics, sourceMapData.sourceMapFilePath, serializeSourceMapContents(

let sourceMapText = serializeSourceMapContents(
3,
sourceMapData.sourceMapFile,
sourceMapData.sourceMapSourceRoot,
sourceMapData.sourceMapSources,
sourceMapData.sourceMapNames,
sourceMapData.sourceMapMappings), /*writeByteOrderMark*/ false);
sourceMapData.sourceMapMappings,
sourceMapData.sourceMapSourcesContent);

sourceMapDataList.push(sourceMapData);

let sourceMapUrl: string;
if (compilerOptions.inlineSourceMap) {
// Encode the sourceMap into the sourceMap url
let base64SourceMapText = convertToBase64(sourceMapText);
sourceMapUrl = `//# sourceMappingURL=data:application/json;base64,${base64SourceMapText}`;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yay! Templates!

}
else {
// Write source map file
writeFile(host, diagnostics, sourceMapData.sourceMapFilePath, sourceMapText, /*writeByteOrderMark*/ false);
sourceMapUrl = `//# sourceMappingURL=${sourceMapData.jsSourceMappingURL}`;
}

// Write sourcemap url to the js file and write the js file
writeJavaScriptFile(emitOutput + "//# sourceMappingURL=" + sourceMapData.jsSourceMappingURL, writeByteOrderMark);
writeJavaScriptFile(emitOutput + sourceMapUrl, writeByteOrderMark);
}

// Initialize source map data
Expand All @@ -630,6 +657,7 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {
inputSourceFileNames: [],
sourceMapNames: [],
sourceMapMappings: "",
sourceMapSourcesContent: undefined,
sourceMapDecodedMappings: []
};

Expand Down Expand Up @@ -874,7 +902,7 @@ if (typeof __param !== "function") __param = function (paramIndex, decorator) {
function emitLiteral(node: LiteralExpression) {
let text = getLiteralText(node);

if (compilerOptions.sourceMap && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
if ((compilerOptions.sourceMap || compilerOptions.inlineSourceMap) && (node.kind === SyntaxKind.StringLiteral || isTemplateLiteralKind(node.kind))) {
writer.writeLiteral(text);
}
// For versions below ES6, emit binary & octal literals in their canonical decimal form.
Expand Down
19 changes: 19 additions & 0 deletions src/compiler/program.ts
Original file line number Diff line number Diff line change
Expand Up @@ -534,6 +534,25 @@ module ts {
}
}

if (options.inlineSourceMap) {
if (options.sourceMap) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_sourceMap_cannot_be_specified_with_option_inlineSourceMap));
}
if (options.mapRoot) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_mapRoot_cannot_be_specified_with_option_inlineSourceMap));
}
if (options.sourceRoot) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_sourceRoot_cannot_be_specified_with_option_inlineSourceMap));
}
}


if (options.inlineSources) {
if (!options.sourceMap && !options.inlineSourceMap) {
diagnostics.add(createCompilerDiagnostic(Diagnostics.Option_inlineSources_can_only_be_used_when_either_option_inlineSourceMap_or_option_sourceMap_is_provided));
}
}

if (!options.sourceMap && (options.mapRoot || options.sourceRoot)) {
// Error to specify --mapRoot or --sourceRoot without mapSourceFiles
if (options.mapRoot) {
Expand Down
19 changes: 11 additions & 8 deletions src/compiler/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1096,14 +1096,15 @@ module ts {
}

export interface SourceMapData {
sourceMapFilePath: string; // Where the sourcemap file is written
jsSourceMappingURL: string; // source map URL written in the .js file
sourceMapFile: string; // Source map's file field - .js file name
sourceMapSourceRoot: string; // Source map's sourceRoot field - location where the sources will be present if not ""
sourceMapSources: string[]; // Source map's sources field - list of sources that can be indexed in this source map
inputSourceFileNames: string[]; // Input source file (which one can use on program to get the file), 1:1 mapping with the sourceMapSources list
sourceMapNames?: string[]; // Source map's names field - list of names that can be indexed in this source map
sourceMapMappings: string; // Source map's mapping field - encoded source map spans
sourceMapFilePath: string; // Where the sourcemap file is written
jsSourceMappingURL: string; // source map URL written in the .js file
sourceMapFile: string; // Source map's file field - .js file name
sourceMapSourceRoot: string; // Source map's sourceRoot field - location where the sources will be present if not ""
sourceMapSources: string[]; // Source map's sources field - list of sources that can be indexed in this source map
sourceMapSourcesContent?: string[]; // Source map's sourcesContent field - list of the sources' text to be embedded in the source map
inputSourceFileNames: string[]; // Input source file (which one can use on program to get the file), 1:1 mapping with the sourceMapSources list
sourceMapNames?: string[]; // Source map's names field - list of names that can be indexed in this source map
sourceMapMappings: string; // Source map's mapping field - encoded source map spans
sourceMapDecodedMappings: SourceMapSpan[]; // Raw source map spans that were encoded into the sourceMapMappings
}

Expand Down Expand Up @@ -1647,6 +1648,8 @@ module ts {
diagnostics?: boolean;
emitBOM?: boolean;
help?: boolean;
inlineSourceMap?: boolean;
inlineSources?: boolean;
listFiles?: boolean;
locale?: string;
mapRoot?: string;
Expand Down
77 changes: 77 additions & 0 deletions src/compiler/utilities.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1695,6 +1695,83 @@ module ts {
export function getLocalSymbolForExportDefault(symbol: Symbol) {
return symbol && symbol.valueDeclaration && (symbol.valueDeclaration.flags & NodeFlags.Default) ? symbol.valueDeclaration.localSymbol : undefined;
}

/**
* Replace each instance of non-ascii characters by one, two, three, or four escape sequences
* representing the UTF-8 encoding of the character, and return the expanded char code list.
*/
function getExpandedCharCodes(input: string): number[] {
let output: number[] = [];
let length = input.length;
let leadSurrogate: number = undefined;

for (let i = 0; i < length; i++) {
let charCode = input.charCodeAt(i);

// handel utf8
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

handle

if (charCode < 0x80) {
output.push(charCode);
}
else if (charCode < 0x800) {
output.push((charCode >> 6) | 0B11000000);
output.push((charCode & 0B00111111) | 0B10000000);
}
else if (charCode < 0x10000) {
output.push((charCode >> 12) | 0B11100000);
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
output.push((charCode & 0B00111111) | 0B10000000);
}
else if (charCode < 0x20000) {
output.push((charCode >> 18) | 0B11110000);
output.push(((charCode >> 12) & 0B00111111) | 0B10000000);
output.push(((charCode >> 6) & 0B00111111) | 0B10000000);
output.push((charCode & 0B00111111) | 0B10000000);
}
else {
Debug.assert(false, "Unexpected code point");
}
}

return output;
}

const base64Digits = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=";

/**
* Converts a string to a base-64 encoded ASCII string.
*/
export function convertToBase64(input: string): string {
var result = "";
let charCodes = getExpandedCharCodes(input);
let i = 0;
let length = charCodes.length;
let byte1: number, byte2: number, byte3: number, byte4: number;

while (i < length) {
// Convert every 6-bits in the input 3 character points
// into a base64 digit
byte1 = charCodes[i] >> 2;
byte2 = (charCodes[i] & 0B00000011) << 4 | charCodes[i + 1] >> 4;
byte3 = (charCodes[i + 1] & 0B00001111) << 2 | charCodes[i + 2] >> 6;
byte4 = charCodes[i + 2] & 0B00111111;

// We are out of characters in the input, set the extra
// digits to 64 (padding character).
if (i + 1 >= length) {
byte3 = byte4 = 64;
}
else if (i + 2 >= length) {
byte4 = 64;
}

// Write to the ouput
result += base64Digits.charAt(byte1) + base64Digits.charAt(byte2) + base64Digits.charAt(byte3) + base64Digits.charAt(byte4);

i += 3;
}

return result;
}
}

module ts {
Expand Down
10 changes: 8 additions & 2 deletions src/harness/compilerRunner.ts
Original file line number Diff line number Diff line change
Expand Up @@ -159,7 +159,7 @@ class CompilerBaselineRunner extends RunnerBase {

// Source maps?
it('Correct sourcemap content for ' + fileName, () => {
if (options.sourceMap) {
if (options.sourceMap || options.inlineSourceMap) {
Harness.Baseline.runBaseline('Correct sourcemap content for ' + fileName, justName.replace(/\.ts$/, '.sourcemap.txt'), () => {
var record = result.getSourceMapRecord();
if (options.noEmitOnError && result.errors.length !== 0 && record === undefined) {
Expand Down Expand Up @@ -228,7 +228,13 @@ class CompilerBaselineRunner extends RunnerBase {
});

it('Correct Sourcemap output for ' + fileName, () => {
if (options.sourceMap) {
if (options.inlineSourceMap) {
if (result.sourceMaps.length > 0) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would replace it with Debug.assert with custom message

throw new Error('No sourcemap files should be generated if inlineSourceMaps was set.');
}
return null;
}
else if (options.sourceMap) {
if (result.sourceMaps.length !== result.files.length) {
throw new Error('Number of sourcemap files should be same as js files.');
}
Expand Down
Loading