Skip to content

Commit

Permalink
feat(reporter): support source maps (rewrite stack traces)
Browse files Browse the repository at this point in the history
Closes #594
  • Loading branch information
vojtajina committed Dec 24, 2013
1 parent 46d25b4 commit 70e4abd
Show file tree
Hide file tree
Showing 3 changed files with 80 additions and 15 deletions.
58 changes: 47 additions & 11 deletions lib/reporter.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,58 @@
var util = require('util');
var log = require('./logger').create('reporter');
var MultiReporter = require('./reporters/multi');
var baseReporterDecoratorFactory = require('./reporters/base').decoratorFactory;
var SourceMapConsumer = require('source-map').SourceMapConsumer;

var createErrorFormatter = function(basePath) {
var URL_REGEXP = new RegExp('http:\\/\\/[^\\/]*' +
'\\/(base|absolute)([^\\?\\s\\:]*)(\\?\\w*)?', 'g');
var createErrorFormatter = function(basePath, emitter, SourceMapConsumer) {
var lastServedFiles = [];

emitter.on('file_list_modified', function(filesPromise) {
filesPromise.then(function(files) {
lastServedFiles = files.served;
});
});

var findFile = function(path) {
for (var i = 0; i < lastServedFiles.length; i++) {
if (lastServedFiles[i].path === path) {
return lastServedFiles[i];
}
}
return null;
};

var URL_REGEXP = new RegExp('http:\\/\\/[^\\/]*\\/' +
'(base|absolute)' + // prefix
'([^\\?\\s\\:]*)' + // path
'(\\?\\w*)?' + // sha
'(\\:(\\d+))?' + // line
'(\\:(\\d+))?' + // column
'', 'g');

return function(msg, indentation) {
// remove domain and timestamp from source files
// and resolve base path / absolute path urls into absolute path
msg = (msg || '').replace(URL_REGEXP, function(full, prefix, path) {
msg = (msg || '').replace(URL_REGEXP, function(_, prefix, path, __, ___, line, ____, column) {

if (prefix === 'base') {
return basePath + path;
} else if (prefix === 'absolute') {
return path;
path = basePath + path;
}

var file = findFile(path);

if (file && file.sourceMap) {
line = parseInt(line || '0', 10);
column = parseInt(column || '0', 10);

var smc = new SourceMapConsumer(file.sourceMap);
var original = smc.originalPositionFor({line: line, column: column});

return util.format('%s:%d:%d <- %s:%d:%d', path, line, column, original.source,
original.line, original.column);
}

return path + (line ? ':' + line : '') + (column ? ':' + column : '');
});

// indent every line
Expand All @@ -26,11 +64,9 @@ var createErrorFormatter = function(basePath) {
};
};

createErrorFormatter.$inject = ['config.basePath'];


var createReporters = function(names, config, emitter, injector) {
var errorFormatter = createErrorFormatter(config.basePath, config.urlRoot);
var errorFormatter = createErrorFormatter(config.basePath, emitter, SourceMapConsumer);
var reporters = [];

// TODO(vojta): instantiate all reporters through DI
Expand All @@ -42,7 +78,7 @@ var createReporters = function(names, config, emitter, injector) {

var locals = {
baseReporterDecorator: ['factory', baseReporterDecoratorFactory],
formatError: ['factory', createErrorFormatter]
formatError: ['value', errorFormatter]
};

try {
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -119,7 +119,8 @@
"log4js": "~0.6.3",
"useragent": "~2.0.4",
"graceful-fs": "~1.2.1",
"connect": "~2.8.4"
"connect": "~2.8.4",
"source-map": "~0.1.31"
},
"devDependencies": {
"grunt": "~0.4",
Expand Down
34 changes: 31 additions & 3 deletions test/unit/reporter.spec.coffee
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@
# lib/reporter.js module
#==============================================================================
describe 'reporter', ->
EventEmitter = require('events').EventEmitter
File = require('../../lib/file_list').File
loadFile = require('mocks').loadFile
q = require 'q'
m = null

beforeEach ->
Expand All @@ -13,10 +16,11 @@ describe 'reporter', ->
# formatError() [PRIVATE]
#==============================================================================
describe 'formatError', ->
formatError = null
formatError = emitter = null

beforeEach ->
formatError = m.createErrorFormatter '', '/'
emitter = new EventEmitter
formatError = m.createErrorFormatter '', emitter


it 'should indent', ->
Expand Down Expand Up @@ -52,7 +56,7 @@ describe 'reporter', ->


it 'should restore base paths', ->
formatError = m.createErrorFormatter '/some/base', '/'
formatError = m.createErrorFormatter '/some/base', emitter
expect(formatError 'at http://localhost:123/base/a.js?123').to.equal 'at /some/base/a.js\n'


Expand All @@ -64,3 +68,27 @@ describe 'reporter', ->
it 'should preserve line numbers', ->
ERROR = 'at http://local:1233/absolute/usr/path.js?6e31cb249ee5b32d91f37ea516ca0f84bddc5aa9:2'
expect(formatError ERROR).to.equal 'at /usr/path.js:2\n'


describe 'source maps', ->

class MockSourceMapConsumer
constructor: (sourceMap) ->
@source = sourceMap.replace 'SOURCE MAP ', '/original/'
originalPositionFor: (position) ->
source: @source
line: position.line + 2
column: position.column + 2

it 'should rewrite stack traces', (done) ->
formatError = m.createErrorFormatter '/some/base', emitter, MockSourceMapConsumer
servedFiles = [new File('/some/base/a.js'), new File('/some/base/b.js')]
servedFiles[0].sourceMap = 'SOURCE MAP a.js'
servedFiles[1].sourceMap = 'SOURCE MAP b.js'

emitter.emit 'file_list_modified', q(served: servedFiles)

scheduleNextTick ->
ERROR = 'at http://localhost:123/base/b.js:2:6'
expect(formatError ERROR).to.equal 'at /some/base/b.js:2:6 <- /original/b.js:4:8\n'
done()

0 comments on commit 70e4abd

Please sign in to comment.