diff --git a/toolkit/devtools/sourcemap/SourceMap.jsm b/toolkit/devtools/sourcemap/SourceMap.jsm index 5b006681a40..dd7c525b999 100644 --- a/toolkit/devtools/sourcemap/SourceMap.jsm +++ b/toolkit/devtools/sourcemap/SourceMap.jsm @@ -31,15 +31,6 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour var ArraySet = require('source-map/array-set').ArraySet; var base64VLQ = require('source-map/base64-vlq'); - // TODO: bug 673487 - // - // Sometime in the future, if we decide we need to be able to query where in - // the generated source a piece of the original code came from, we may want to - // add a slot `_originalMappings` which would be an object keyed by the - // original source and whose value would be an array of mappings ordered by - // original line/col rather than generated (which is what we have now in - // `_generatedMappings`). - /** * A SourceMapConsumer instance represents a parsed source map which we can * query for information about the original file positions by giving it a file @@ -72,7 +63,7 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour function SourceMapConsumer(aSourceMap) { var sourceMap = aSourceMap; if (typeof aSourceMap === 'string') { - sourceMap = JSON.parse(aSourceMap); + sourceMap = JSON.parse(aSourceMap.replace(/^\)\]\}'/, '')); } var version = util.getArg(sourceMap, 'version'); @@ -89,9 +80,9 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour this._names = ArraySet.fromArray(names); this._sources = ArraySet.fromArray(sources); - // `this._generatedMappings` hold the parsed mapping coordinates from the - // source map's "mappings" attribute. Each object in the array is of the - // form + // `this._generatedMappings` and `this._originalMappings` hold the parsed + // mapping coordinates from the source map's "mappings" attribute. Each + // object in the array is of the form // // { // generatedLine: The line number in the generated code, @@ -108,7 +99,12 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour // // All properties except for `generatedLine` and `generatedColumn` can be // `null`. + // + // `this._generatedMappings` is ordered by the generated positions. + // + // `this._originalMappings` is ordered by the original positions. this._generatedMappings = []; + this._originalMappings = []; this._parseMappings(mappings, sourceRoot); } @@ -195,8 +191,65 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour } this._generatedMappings.push(mapping); + this._originalMappings.push(mapping); } } + + this._originalMappings.sort(this._compareOriginalPositions); + }; + + /** + * Comparator between two mappings where the original positions are compared. + */ + SourceMapConsumer.prototype._compareOriginalPositions = + function SourceMapConsumer_compareOriginalPositions(mappingA, mappingB) { + if (mappingA.source > mappingB.source) { + return 1; + } + else if (mappingA.source < mappingB.source) { + return -1; + } + else { + var cmp = mappingA.originalLine - mappingB.originalLine; + return cmp === 0 + ? mappingA.originalColumn - mappingB.originalColumn + : cmp; + } + }; + + /** + * Comparator between two mappings where the generated positions are compared. + */ + SourceMapConsumer.prototype._compareGeneratedPositions = + function SourceMapConsumer_compareGeneratedPositions(mappingA, mappingB) { + var cmp = mappingA.generatedLine - mappingB.generatedLine; + return cmp === 0 + ? mappingA.generatedColumn - mappingB.generatedColumn + : cmp; + }; + + /** + * Find the mapping that best matches the hypothetical "needle" mapping that + * we are searching for in the given "haystack" of mappings. + */ + SourceMapConsumer.prototype._findMapping = + function SourceMapConsumer_findMapping(aNeedle, aMappings, aLineName, + aColumnName, aComparator) { + // To return the position we are searching for, we must first find the + // mapping for the given position and then return the opposite position it + // points to. Because the mappings are sorted, we can use binary search to + // find the best mapping. + + if (aNeedle[aLineName] <= 0) { + throw new TypeError('Line must be greater than or equal to 1, got ' + + aNeedle[aLineName]); + } + if (aNeedle[aColumnName] < 0) { + throw new TypeError('Column must be greater than or equal to 0, got ' + + aNeedle[aColumnName]); + } + + return binarySearch.search(aNeedle, aMappings, aComparator); }; /** @@ -216,35 +269,16 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour */ SourceMapConsumer.prototype.originalPositionFor = function SourceMapConsumer_originalPositionFor(aArgs) { - // To return the original position, we must first find the mapping for the - // given generated position and then return the original position it - // points to. Because the mappings are sorted by generated line/column, we - // can use binary search to find the best mapping. - - // To perform a binary search on the mappings, we must be able to compare - // two mappings. - function compare(mappingA, mappingB) { - var cmp = mappingA.generatedLine - mappingB.generatedLine; - return cmp === 0 - ? mappingA.generatedColumn - mappingB.generatedColumn - : cmp; - } - - // This is the mock of the mapping we are looking for: the needle in the - // haystack of mappings. var needle = { generatedLine: util.getArg(aArgs, 'line'), generatedColumn: util.getArg(aArgs, 'column') }; - if (needle.generatedLine <= 0) { - throw new TypeError('Line must be greater than or equal to 1.'); - } - if (needle.generatedColumn < 0) { - throw new TypeError('Column must be greater than or equal to 0.'); - } - - var mapping = binarySearch.search(needle, this._generatedMappings, compare); + var mapping = this._findMapping(needle, + this._generatedMappings, + "generatedLine", + "generatedColumn", + this._compareGeneratedPositions) if (mapping) { return { @@ -261,7 +295,47 @@ define('source-map/source-map-consumer', ['require', 'exports', 'module' , 'sour column: null, name: null }; + }; + /** + * Returns the generated line and column information for the original source, + * line, and column positions provided. The only argument is an object with + * the following properties: + * + * - source: The filename of the original source. + * - line: The line number in the original source. + * - column: The column number in the original source. + * + * and an object is returned with the following properties: + * + * - line: The line number in the generated source, or null. + * - column: The column number in the generated source, or null. + */ + SourceMapConsumer.prototype.generatedPositionFor = + function SourceMapConsumer_generatedPositionFor(aArgs) { + var needle = { + source: util.getArg(aArgs, 'source'), + originalLine: util.getArg(aArgs, 'line'), + originalColumn: util.getArg(aArgs, 'column') + }; + + var mapping = this._findMapping(needle, + this._originalMappings, + "originalLine", + "originalColumn", + this._compareOriginalPositions) + + if (mapping) { + return { + line: util.getArg(mapping, 'generatedLine', null), + column: util.getArg(mapping, 'generatedColumn', null) + }; + } + + return { + line: null, + column: null + }; }; exports.SourceMapConsumer = SourceMapConsumer; @@ -885,7 +959,9 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/s }, this); } else if (aChunk instanceof SourceNode || typeof aChunk === "string") { - this.children.push(aChunk); + if (aChunk) { + this.children.push(aChunk); + } } else { throw new TypeError( @@ -973,7 +1049,7 @@ define('source-map/source-node', ['require', 'exports', 'module' , 'source-map/s lastChild.replaceRight(aPattern, aReplacement); } else if (typeof lastChild === 'string') { - this.children[this.children.lenth - 1] = lastChild.replace(aPattern, aReplacement); + this.children[this.children.length - 1] = lastChild.replace(aPattern, aReplacement); } else { this.children.push(''.replace(aPattern, aReplacement)); diff --git a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm index 83a14c628e4..90d2d36b14a 100644 --- a/toolkit/devtools/sourcemap/tests/unit/Utils.jsm +++ b/toolkit/devtools/sourcemap/tests/unit/Utils.jsm @@ -13,7 +13,7 @@ */ Components.utils.import('resource://gre/modules/devtools/Require.jsm'); -Components.utils.import('resource:///modules/devtools/SourceMap.jsm'); +Components.utils.import('resource://gre/modules/devtools/SourceMap.jsm'); let EXPORTED_SYMBOLS = [ "define", "runSourceMapTests" ]; /* -*- Mode: js; js-indent-level: 2; -*- */ @@ -109,23 +109,40 @@ define('test/source-map/util', ['require', 'exports', 'module' ], function(requi }; function assertMapping(generatedLine, generatedColumn, originalSource, - originalLine, originalColumn, name, map, assert) { - var mapping = map.originalPositionFor({ - line: generatedLine, - column: generatedColumn - }); - assert.equal(mapping.name, name, - 'Incorrect name, expected ' + JSON.stringify(name) - + ', got ' + JSON.stringify(mapping.name)); - assert.equal(mapping.line, originalLine, - 'Incorrect line, expected ' + JSON.stringify(originalLine) - + ', got ' + JSON.stringify(mapping.line)); - assert.equal(mapping.column, originalColumn, - 'Incorrect column, expected ' + JSON.stringify(originalColumn) - + ', got ' + JSON.stringify(mapping.column)); - assert.equal(mapping.source, originalSource, - 'Incorrect source, expected ' + JSON.stringify(originalSource) - + ', got ' + JSON.stringify(mapping.source)); + originalLine, originalColumn, name, map, assert, + dontTestGenerated, dontTestOriginal) { + if (!dontTestOriginal) { + var origMapping = map.originalPositionFor({ + line: generatedLine, + column: generatedColumn + }); + assert.equal(origMapping.name, name, + 'Incorrect name, expected ' + JSON.stringify(name) + + ', got ' + JSON.stringify(origMapping.name)); + assert.equal(origMapping.line, originalLine, + 'Incorrect line, expected ' + JSON.stringify(originalLine) + + ', got ' + JSON.stringify(origMapping.line)); + assert.equal(origMapping.column, originalColumn, + 'Incorrect column, expected ' + JSON.stringify(originalColumn) + + ', got ' + JSON.stringify(origMapping.column)); + assert.equal(origMapping.source, originalSource, + 'Incorrect source, expected ' + JSON.stringify(originalSource) + + ', got ' + JSON.stringify(origMapping.source)); + } + + if (!dontTestGenerated) { + var genMapping = map.generatedPositionFor({ + source: originalSource, + line: originalLine, + column: originalColumn + }); + assert.equal(genMapping.line, generatedLine, + 'Incorrect line, expected ' + JSON.stringify(generatedLine) + + ', got ' + JSON.stringify(genMapping.line)); + assert.equal(genMapping.column, generatedColumn, + 'Incorrect column, expected ' + JSON.stringify(generatedColumn) + + ', got ' + JSON.stringify(genMapping.column)); + } } exports.assertMapping = assertMapping; diff --git a/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js b/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js index 99087845866..8fb24f4c587 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_dog_fooding.js @@ -56,14 +56,22 @@ define("test/source-map/test-dog-fooding", ["require", "exports", "module"], fun util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 0, null, smc, assert); // Fuzzy - util.assertMapping(2, 0, null, null, null, null, smc, assert); - util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert); - util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert); - util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert); - util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert); - util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert); - util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert); - util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert); + + // Original to generated + util.assertMapping(2, 0, null, null, null, null, smc, assert, true); + util.assertMapping(2, 9, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true); + util.assertMapping(3, 0, '/wu/tang/gza.coffee', 1, 0, null, smc, assert, true); + util.assertMapping(3, 9, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true); + util.assertMapping(4, 0, '/wu/tang/gza.coffee', 2, 0, null, smc, assert, true); + util.assertMapping(4, 9, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true); + util.assertMapping(5, 0, '/wu/tang/gza.coffee', 3, 0, null, smc, assert, true); + util.assertMapping(5, 9, '/wu/tang/gza.coffee', 4, 0, null, smc, assert, true); + + // Generated to original + util.assertMapping(2, 2, '/wu/tang/gza.coffee', 1, 1, null, smc, assert, null, true); + util.assertMapping(3, 2, '/wu/tang/gza.coffee', 2, 3, null, smc, assert, null, true); + util.assertMapping(4, 2, '/wu/tang/gza.coffee', 3, 6, null, smc, assert, null, true); + util.assertMapping(5, 2, '/wu/tang/gza.coffee', 4, 9, null, smc, assert, null, true); }; }); diff --git a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js index 35824cbb562..1e1d92b85c3 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_source_map_consumer.js @@ -61,12 +61,24 @@ define("test/source-map/test-source-map-consumer", ["require", "exports", "modul util.assertMapping(2, 28, '/the/root/two.js', 2, 10, 'n', map, assert); }; - exports['test mapping tokens back fuzzy'] = function (assert, util) { + exports['test mapping tokens fuzzy'] = function (assert, util) { var map = new SourceMapConsumer(util.testMap); - util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert); - util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert); - util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert); + // Finding original positions + util.assertMapping(1, 20, '/the/root/one.js', 1, 21, 'bar', map, assert, true); + util.assertMapping(1, 30, '/the/root/one.js', 2, 10, 'baz', map, assert, true); + util.assertMapping(2, 12, '/the/root/two.js', 1, 11, null, map, assert, true); + + // Finding generated positions + util.assertMapping(1, 18, '/the/root/one.js', 1, 22, 'bar', map, assert, null, true); + util.assertMapping(1, 28, '/the/root/one.js', 2, 13, 'baz', map, assert, null, true); + util.assertMapping(2, 9, '/the/root/two.js', 1, 16, null, map, assert, null, true); + }; + + exports['test creating source map consumers with )]}\' prefix'] = function (assert, util) { + assert.doesNotThrow(function () { + var map = new SourceMapConsumer(")]}'" + JSON.stringify(util.testMap)); + }); }; }); diff --git a/toolkit/devtools/sourcemap/tests/unit/test_source_node.js b/toolkit/devtools/sourcemap/tests/unit/test_source_node.js index 60f7cd41284..afdbc781d9d 100644 --- a/toolkit/devtools/sourcemap/tests/unit/test_source_node.js +++ b/toolkit/devtools/sourcemap/tests/unit/test_source_node.js @@ -117,6 +117,22 @@ define("test/source-map/test-source-node", ["require", "exports", "module"], fun }); }; + exports['test .replaceRight'] = function (assert, util) { + var node; + + // Not nested + node = new SourceNode(null, null, null, 'hello world'); + node.replaceRight(/world/, 'universe'); + assert.equal(node.toString(), 'hello universe'); + + // Nested + node = new SourceNode(null, null, null, + [new SourceNode(null, null, null, 'hey sexy mama, '), + new SourceNode(null, null, null, 'want to kill all humans?')]); + node.replaceRight(/kill all humans/, 'watch Futurama'); + assert.equal(node.toString(), 'hey sexy mama, want to watch Futurama?'); + }; + exports['test .toStringWithSourceMap()'] = function (assert, util) { var node = new SourceNode(null, null, null, ['(function () {\n',