bug 673487 - map from an original source to a generated; r=robcee

This commit is contained in:
Nick Fitzgerald 2012-09-27 06:15:00 +01:00
parent 03ae5283f5
commit 7d0ac3196f
5 changed files with 198 additions and 69 deletions

View File

@ -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));

View File

@ -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;

View File

@ -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);
};
});

View File

@ -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));
});
};
});

View File

@ -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',