Skip to content
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ node_modules/
out/
.vscode-test/
coverage/
.idea

# Sample Application auto-generated files
SampleApplication/.vscode/.react/debuggerWorker.js
Expand Down
31 changes: 25 additions & 6 deletions src/debugger/sourceMapsCombinator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@ import * as path from "path";
import { SourceMapConsumer, RawSourceMap, SourceMapGenerator, MappingItem, Mapping, Position, MappedPosition } from "source-map";
import sourceMapResolve = require("source-map-resolve");

const DISK_LETTER_RE: RegExp = /^[a-z]:/i;

export class SourceMapsCombinator {

public convert(rawBundleSourcemap: RawSourceMap): RawSourceMap {
Expand Down Expand Up @@ -59,11 +61,13 @@ export class SourceMapsCombinator {

// Resolve TS source path to absolute because it might be relative to generated JS
// (this depends on whether "sourceRoot" option is specified in tsconfig.json)
tsPosition.source = path.resolve(
rawBundleSourcemap.sourceRoot,
path.dirname(item.source),
tsPosition.source
);
if (!tsPosition.source.match(DISK_LETTER_RE)) { // This check for Windows tests which were run on MacOs
tsPosition.source = path.resolve(
rawBundleSourcemap.sourceRoot,
path.dirname(item.source),
tsPosition.source
);
}

// Update mapping w/ mapped position values
mapping = {
Expand All @@ -90,10 +94,25 @@ export class SourceMapsCombinator {
}

private readSourcemap(file: string, code: string): SourceMapConsumer | null {
let result = sourceMapResolve.resolveSync(code, file, fs.readFileSync);
let result = sourceMapResolve.resolveSync(code, file, readFileSync.bind(null, getDiskLetter(file)));
if (result === null) {
return null;
}
return new SourceMapConsumer(result.map);
}
}

// Hack for source-map-resolve and cutted disk letter
// https://github.com/lydell/source-map-resolve/issues/9
function readFileSync(diskLetter: string, filePath: string) {
if (filePath.match(DISK_LETTER_RE)) {
return fs.readFileSync(filePath);
} else {
return fs.readFileSync(`${diskLetter}${filePath}`);
}
}

function getDiskLetter(filePath: string): string {
const matched = filePath.match(DISK_LETTER_RE);
return matched ? matched[0] : "";
}
13 changes: 13 additions & 0 deletions src/test/debugger/assets/hello.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.
class Hello {
constructor (public msg: string) {
}
public sayHello() {
return this.msg;
}
}

const hello = new Hello("HelloWorld!");

console.log(hello.sayHello());
70 changes: 70 additions & 0 deletions src/test/debugger/sourceMapsCombinator.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT license. See LICENSE file in the project root for details.

import { SourceMapsCombinator } from "../../debugger/sourceMapsCombinator";

import * as assert from "assert";
import * as sinon from "sinon";
import * as fs from "fs";
import * as path from "path";

suite("sourceMapsCombinator", function () {
let sandbox: Sinon.SinonSandbox;

setup(() => {
sandbox = sinon.sandbox.create();
});

suiteTeardown(() => {
sandbox.restore();
});

suite("#convert", function () {
test("convert sourcemap", function () {
const pathToJS = "d:/hello.js";
const pathToTS = "d:/hello.ts";
const sourcemapPath = "d:/hello.js.map";
const codeJS = fs.readFileSync(path.resolve(__dirname, "assets/hello.js"));
const codeTS = fs.readFileSync(path.resolve(__dirname, "../../../src/test/debugger/assets/hello.ts"));
const sourcemap = {
"version": 3,
"sources": [
"d:/hello.ts",
],
"names": [],
"mappings": "AAAA,MAAM,MAAM;IACR,YAAY,CAAC,OAAO,GAAG,EAAE,MAAM,CAAC;IAChC;IACA,OAAO,QAAQ,CAAC,EAAE;QACd,OAAO,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO;IACtC;AACJ;;AAEA,MAAM,MAAM,EAAE,IAAI,KAAK,CAAC,gDAAgD,CAAC;;AAEzE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC", "file": "hello.js", "sourceRoot": "",
};

const expected = {
"version": 3,
"sources": [
"d:/hello.ts",
],
"names": [],
"mappings": "AAAA,IAAA,MAAM,EAAM,CAAA,SAAA,CAAA,EAAA;IACR,SAAA,KAAa,CAAA,GAAA,EAAO;QACpB,IAAA,CAAA,IAAA,EAAA,GAAA;IACA;SACI,CAAA,SAAO,CAAA,SAAc,EAAA,SAAM,CAAA,EAAO;QACtC,OAAA,OAAA,EAAA,IAAA,CAAA,IAAA,EAAA,OAAA;IACJ,CAAA;;AAEA,CAAA,CAAA,CAAA,CAAA;;AAEA,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC",
};

let rawBundleSourcemap = {
"version": 3,
"sources": [
"d:/hello.js",
],
"names": [],
"mappings": "AAAA,IAAI,MAAM,EAAE,CAAC,SAAS,CAAC,EAAE;IACrB,SAAS,KAAK,CAAC,GAAG,EAAE;QAChB,IAAI,CAAC,IAAI,EAAE,GAAG;IAClB;IACA,KAAK,CAAC,SAAS,CAAC,SAAS,EAAE,SAAS,CAAC,EAAE;QACnC,OAAO,OAAO,EAAE,IAAI,CAAC,IAAI,EAAE,OAAO;IACtC,CAAC;IACD,OAAO,KAAK;AAChB,CAAC,CAAC,CAAC,CAAC;AACJ,IAAI,MAAM,EAAE,IAAI,KAAK,CAAC,gDAAgD,CAAC;AACvE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,QAAQ,CAAC,CAAC,CAAC", "file": "hello.js", "sourceRoot": "",
};

const fsReadFileStub = sandbox.stub(fs, "readFileSync");

fsReadFileStub.withArgs(pathToJS).returns(codeJS);
fsReadFileStub.withArgs(pathToTS).returns(codeTS);
fsReadFileStub.withArgs(sourcemapPath).returns(JSON.stringify(sourcemap));

let sourceMapsCombinator = new SourceMapsCombinator();
let result = sourceMapsCombinator.convert(rawBundleSourcemap);
result.sources = result.sources.map(p => {
return p.replace(/\\/g, "/");
});
assert.deepEqual(expected, result);
});
});
});