Skip to content

Commit 70e4abd

Browse files
committed
feat(reporter): support source maps (rewrite stack traces)
Closes #594
1 parent 46d25b4 commit 70e4abd

File tree

3 files changed

+80
-15
lines changed

3 files changed

+80
-15
lines changed

lib/reporter.js

Lines changed: 47 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,20 +1,58 @@
1+
var util = require('util');
12
var log = require('./logger').create('reporter');
23
var MultiReporter = require('./reporters/multi');
34
var baseReporterDecoratorFactory = require('./reporters/base').decoratorFactory;
5+
var SourceMapConsumer = require('source-map').SourceMapConsumer;
46

5-
var createErrorFormatter = function(basePath) {
6-
var URL_REGEXP = new RegExp('http:\\/\\/[^\\/]*' +
7-
'\\/(base|absolute)([^\\?\\s\\:]*)(\\?\\w*)?', 'g');
7+
var createErrorFormatter = function(basePath, emitter, SourceMapConsumer) {
8+
var lastServedFiles = [];
9+
10+
emitter.on('file_list_modified', function(filesPromise) {
11+
filesPromise.then(function(files) {
12+
lastServedFiles = files.served;
13+
});
14+
});
15+
16+
var findFile = function(path) {
17+
for (var i = 0; i < lastServedFiles.length; i++) {
18+
if (lastServedFiles[i].path === path) {
19+
return lastServedFiles[i];
20+
}
21+
}
22+
return null;
23+
};
24+
25+
var URL_REGEXP = new RegExp('http:\\/\\/[^\\/]*\\/' +
26+
'(base|absolute)' + // prefix
27+
'([^\\?\\s\\:]*)' + // path
28+
'(\\?\\w*)?' + // sha
29+
'(\\:(\\d+))?' + // line
30+
'(\\:(\\d+))?' + // column
31+
'', 'g');
832

933
return function(msg, indentation) {
1034
// remove domain and timestamp from source files
1135
// and resolve base path / absolute path urls into absolute path
12-
msg = (msg || '').replace(URL_REGEXP, function(full, prefix, path) {
36+
msg = (msg || '').replace(URL_REGEXP, function(_, prefix, path, __, ___, line, ____, column) {
37+
1338
if (prefix === 'base') {
14-
return basePath + path;
15-
} else if (prefix === 'absolute') {
16-
return path;
39+
path = basePath + path;
40+
}
41+
42+
var file = findFile(path);
43+
44+
if (file && file.sourceMap) {
45+
line = parseInt(line || '0', 10);
46+
column = parseInt(column || '0', 10);
47+
48+
var smc = new SourceMapConsumer(file.sourceMap);
49+
var original = smc.originalPositionFor({line: line, column: column});
50+
51+
return util.format('%s:%d:%d <- %s:%d:%d', path, line, column, original.source,
52+
original.line, original.column);
1753
}
54+
55+
return path + (line ? ':' + line : '') + (column ? ':' + column : '');
1856
});
1957

2058
// indent every line
@@ -26,11 +64,9 @@ var createErrorFormatter = function(basePath) {
2664
};
2765
};
2866

29-
createErrorFormatter.$inject = ['config.basePath'];
30-
3167

3268
var createReporters = function(names, config, emitter, injector) {
33-
var errorFormatter = createErrorFormatter(config.basePath, config.urlRoot);
69+
var errorFormatter = createErrorFormatter(config.basePath, emitter, SourceMapConsumer);
3470
var reporters = [];
3571

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

4379
var locals = {
4480
baseReporterDecorator: ['factory', baseReporterDecoratorFactory],
45-
formatError: ['factory', createErrorFormatter]
81+
formatError: ['value', errorFormatter]
4682
};
4783

4884
try {

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -119,7 +119,8 @@
119119
"log4js": "~0.6.3",
120120
"useragent": "~2.0.4",
121121
"graceful-fs": "~1.2.1",
122-
"connect": "~2.8.4"
122+
"connect": "~2.8.4",
123+
"source-map": "~0.1.31"
123124
},
124125
"devDependencies": {
125126
"grunt": "~0.4",

test/unit/reporter.spec.coffee

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,10 @@
22
# lib/reporter.js module
33
#==============================================================================
44
describe 'reporter', ->
5+
EventEmitter = require('events').EventEmitter
6+
File = require('../../lib/file_list').File
57
loadFile = require('mocks').loadFile
8+
q = require 'q'
69
m = null
710

811
beforeEach ->
@@ -13,10 +16,11 @@ describe 'reporter', ->
1316
# formatError() [PRIVATE]
1417
#==============================================================================
1518
describe 'formatError', ->
16-
formatError = null
19+
formatError = emitter = null
1720

1821
beforeEach ->
19-
formatError = m.createErrorFormatter '', '/'
22+
emitter = new EventEmitter
23+
formatError = m.createErrorFormatter '', emitter
2024

2125

2226
it 'should indent', ->
@@ -52,7 +56,7 @@ describe 'reporter', ->
5256

5357

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

5862

@@ -64,3 +68,27 @@ describe 'reporter', ->
6468
it 'should preserve line numbers', ->
6569
ERROR = 'at http://local:1233/absolute/usr/path.js?6e31cb249ee5b32d91f37ea516ca0f84bddc5aa9:2'
6670
expect(formatError ERROR).to.equal 'at /usr/path.js:2\n'
71+
72+
73+
describe 'source maps', ->
74+
75+
class MockSourceMapConsumer
76+
constructor: (sourceMap) ->
77+
@source = sourceMap.replace 'SOURCE MAP ', '/original/'
78+
originalPositionFor: (position) ->
79+
source: @source
80+
line: position.line + 2
81+
column: position.column + 2
82+
83+
it 'should rewrite stack traces', (done) ->
84+
formatError = m.createErrorFormatter '/some/base', emitter, MockSourceMapConsumer
85+
servedFiles = [new File('/some/base/a.js'), new File('/some/base/b.js')]
86+
servedFiles[0].sourceMap = 'SOURCE MAP a.js'
87+
servedFiles[1].sourceMap = 'SOURCE MAP b.js'
88+
89+
emitter.emit 'file_list_modified', q(served: servedFiles)
90+
91+
scheduleNextTick ->
92+
ERROR = 'at http://localhost:123/base/b.js:2:6'
93+
expect(formatError ERROR).to.equal 'at /some/base/b.js:2:6 <- /original/b.js:4:8\n'
94+
done()

0 commit comments

Comments
 (0)