diff --git a/lib/source-map/util.js b/lib/source-map/util.js index 0150216c..047b0544 100644 --- a/lib/source-map/util.js +++ b/lib/source-map/util.js @@ -198,15 +198,30 @@ define(function (require, exports, module) { aRoot = aRoot.replace(/\/$/, ''); - // XXX: It is possible to remove this block, and the tests still pass! - var url = urlParse(aRoot); - if (aPath.charAt(0) == "/" && url && url.path == "/") { - return aPath.slice(1); + // It is possible for the path to be above the root. In this case, simply + // checking whether the root is a prefix of the path won't work. Instead, we + // need to remove components from the root one by one, until either we find + // a prefix that fits, or we run out of components to remove. + var level = 0; + while (aPath.indexOf(aRoot + '/') !== 0) { + var index = aRoot.lastIndexOf("/"); + if (index < 0) { + return aPath; + } + + // If the only part of the root that is left is the scheme (i.e. http://, + // file:///, etc.), one or more slashes (/), or simply nothing at all, we + // have exhausted all components, so the path is not relative to the root. + aRoot = aRoot.slice(0, index); + if (aRoot.match(/^([^\/]+:\/)?\/*$/)) { + return aPath; + } + + ++level; } - return aPath.indexOf(aRoot + '/') === 0 - ? aPath.substr(aRoot.length + 1) - : aPath; + // Make sure we add a "../" for each component we removed from the root. + return Array(level + 1).join("../") + aPath.substr(aRoot.length + 1); } exports.relative = relative; diff --git a/test/source-map/test-source-map-consumer.js b/test/source-map/test-source-map-consumer.js index 2b56713d..5b051de8 100644 --- a/test/source-map/test-source-map-consumer.js +++ b/test/source-map/test-source-map-consumer.js @@ -495,6 +495,29 @@ define(function (require, exports, module) { assert.equal(pos.column, 2); }; + exports['test sourceRoot + generatedPositionFor for path above the root'] = function (assert, util) { + var map = new SourceMapGenerator({ + sourceRoot: 'foo/bar', + file: 'baz.js' + }); + map.addMapping({ + original: { line: 1, column: 1 }, + generated: { line: 2, column: 2 }, + source: '../bang.coffee' + }); + map = new SourceMapConsumer(map.toString()); + + // Should handle with sourceRoot. + var pos = map.generatedPositionFor({ + line: 1, + column: 1, + source: 'foo/bang.coffee' + }); + + assert.equal(pos.line, 2); + assert.equal(pos.column, 2); + }; + exports['test allGeneratedPositionsFor for line'] = function (assert, util) { var map = new SourceMapGenerator({ file: 'generated.js' diff --git a/test/source-map/test-util.js b/test/source-map/test-util.js index 997d1a26..530dbdbc 100644 --- a/test/source-map/test-util.js +++ b/test/source-map/test-util.js @@ -202,7 +202,11 @@ define(function (require, exports, module) { // TODO Issue #128: Define and test this function properly. exports['test relative()'] = function (assert, util) { assert.equal(libUtil.relative('/the/root', '/the/root/one.js'), 'one.js'); - assert.equal(libUtil.relative('/the/root', '/the/rootone.js'), '/the/rootone.js'); + assert.equal(libUtil.relative('http://the/root', 'http://the/root/one.js'), 'one.js'); + assert.equal(libUtil.relative('/the/root', '/the/rootone.js'), '../rootone.js'); + assert.equal(libUtil.relative('http://the/root', 'http://the/rootone.js'), '../rootone.js'); + assert.equal(libUtil.relative('/the/root', '/therootone.js'), '/therootone.js'); + assert.equal(libUtil.relative('http://the/root', '/therootone.js'), '/therootone.js'); assert.equal(libUtil.relative('', '/the/root/one.js'), '/the/root/one.js'); assert.equal(libUtil.relative('.', '/the/root/one.js'), '/the/root/one.js');