Skip to content

Commit 3f1c690

Browse files
author
Eric Wendelin
committed
Implement StackTrace.fromError using ES6Promise.
Use StackTraceGPS for source map support for #44. Filter out stacktrace.js internals by default fixes #65.
1 parent 33d2c9f commit 3f1c690

8 files changed

+187
-101
lines changed

dist/stacktrace.js

+29-41
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,16 @@
1-
/* global StackFrame: false, ErrorStackParser: false */
21
(function (root, factory) {
32
'use strict';
43
// Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
54
if (typeof define === 'function' && define.amd) {
6-
define(['error-stack-parser', 'stack-generator', 'stacktrace-gps'], factory);
5+
define(['error-stack-parser', 'stack-generator', 'stacktrace-gps', 'es6-promise'], factory);
76
} else if (typeof exports === 'object') {
8-
module.exports = factory(require('error-stack-parser'), require('stack-generator'), require('stacktrace-gps'));
7+
module.exports = factory(require('error-stack-parser'), require('stack-generator'), require('stacktrace-gps'), require('es6-promise'));
98
} else {
10-
root.StackTrace = factory(root.ErrorStackParser, root.StackGenerator, root.StackTraceGPS);
9+
root.StackTrace = factory(root.ErrorStackParser, root.StackGenerator, root.StackTraceGPS, root.ES6Promise);
1110
}
12-
}(this, function (ErrorStackParser, StackGenerator, StackTraceGPS) {
13-
// { filter: fnRef
14-
// sourceMap: ???
15-
// cors: ???
16-
// enhancedFunctionNames: true
17-
// enhancedSourceLocations: true
18-
// formatter: fnRef
19-
// }
11+
}(this, function StackTrace(ErrorStackParser, StackGenerator, StackTraceGPS, RSVP) {
12+
ES6Promise.polyfill();
13+
var Promise = ES6Promise.Promise;
2014

2115
/**
2216
* Merge 2 given Objects. If a conflict occurs the second object wins.
@@ -28,10 +22,9 @@
2822
*/
2923
function _merge(first, second) {
3024
var target = {};
31-
var prop;
3225

3326
[first, second].forEach(function (obj) {
34-
for (prop in obj) {
27+
for (var prop in obj) {
3528
if (obj.hasOwnProperty(prop)) {
3629
target[prop] = obj[prop];
3730
}
@@ -42,18 +35,13 @@
4235
return target;
4336
}
4437

45-
/**
46-
* Return true if called from context within strict mode.
47-
* @private
48-
*/
49-
function _isStrictMode() {
50-
return (eval("var __temp = null"), (typeof __temp === "undefined")); // jshint ignore:line
51-
}
52-
5338
return function StackTrace() {
54-
// TODO: utils to facilitate automatic bug reporting
55-
this.gps = undefined;
56-
this.options = {};
39+
this.options = {
40+
filter: function (stackframe) {
41+
// Filter out stackframes for this library by default
42+
return (stackframe.fileName || '').indexOf('stacktrace.') === -1;
43+
}
44+
};
5745

5846
/**
5947
* Get a backtrace from invocation point.
@@ -80,25 +68,25 @@
8068
*/
8169
this.fromError = function fromError(error, opts) {
8270
opts = _merge(this.options, opts);
83-
84-
var stackframes = ErrorStackParser.parse(error);
85-
if (typeof opts.filter === 'function') {
86-
stackframes = stackframes.filter(opts.filter);
87-
}
88-
89-
stackframes.map(function(sf) {
90-
if (typeof this.gps !== 'function') {
91-
this.gps = new StackTraceGPS();
71+
return new Promise(function (resolve) {
72+
var stackframes = ErrorStackParser.parse(error);
73+
if (typeof opts.filter === 'function') {
74+
stackframes = stackframes.filter(opts.filter);
9275
}
93-
this.gps.findFunctionName(sf, function(name) {
94-
sf.setFunctionName(name);
95-
return sf;
96-
}, function(err) {
97-
return sf;
98-
});
76+
77+
resolve(Promise.all(stackframes.map(this.getMappedLocation)));
9978
}.bind(this));
79+
};
10080

101-
return stackframes;
81+
this.getMappedLocation = function getMappedLocation(stackframe) {
82+
return new Promise(function(resolve) {
83+
new StackTraceGPS().getMappedLocation(stackframe)
84+
.then(function (loc) {
85+
resolve(new StackFrame(loc.name, stackframe.args, loc.source, loc.line, loc.column));
86+
})['catch'](function() {
87+
resolve(stackframe);
88+
});
89+
});
10290
};
10391

10492
/**

dist/stacktrace.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

dist/stacktrace.min.js

+2-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

karma.conf.ci.js

+2
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,11 @@ module.exports = function (config) {
2828
frameworks: ['jasmine', 'sinon'],
2929
files: [
3030
'node_modules/error-stack-parser/dist/error-stack-parser.min.js',
31+
'node_modules/es6-promise/dist/es6-promise.js',
3132
'node_modules/stack-generator/dist/stack-generator.min.js',
3233
'node_modules/stacktrace-gps/dist/stacktrace-gps.min.js',
3334
'stacktrace.js',
35+
'spec/spec-helper.js',
3436
'spec/*-spec.js'
3537
],
3638
exclude: [],

karma.conf.js

+1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ module.exports = function (config) {
77
'node_modules/stack-generator/dist/stack-generator.min.js',
88
'node_modules/stacktrace-gps/dist/stacktrace-gps.min.js',
99
'stacktrace.js',
10+
'spec/spec-helper.js',
1011
'spec/*-spec.js'
1112
],
1213
reporters: ['progress', 'coverage'],

package.json

+3-2
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,9 @@
2020
},
2121
"dependencies": {
2222
"error-stack-parser": "~0",
23+
"es6-promise": "~2",
2324
"stack-generator": "~1",
24-
"stacktrace-gps": "~0"
25+
"stacktrace-gps": "~1"
2526
},
2627
"devDependencies": {
2728
"colors": "~1.0.3",
@@ -40,7 +41,7 @@
4041
"karma-safari-launcher": "^0.1.1",
4142
"karma-sauce-launcher": "^0.2.10",
4243
"karma-sinon": "^1.0.3",
43-
"sinon": "^1.10.3",
44+
"sinon": "^1.12.1",
4445
"uglify-js2": "^2.1.11"
4546
},
4647
"bugs": {

spec/stacktrace-spec.js

+120-15
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,21 @@
1-
/* global StackTrace: false */
21
describe('StackTrace', function () {
2+
var callback;
3+
var debugCallback;
4+
var errback;
5+
var debugErrback;
6+
7+
beforeEach(function () {
8+
callback = jasmine.createSpy('callback');
9+
errback = jasmine.createSpy('errback');
10+
debugCallback = function (stackframes) {
11+
console.log(stackframes);
12+
};
13+
debugErrback = function (e) {
14+
console.log(e.message);
15+
console.log(e.stack);
16+
};
17+
});
18+
319
describe('#constructor', function () {
420
it('should allow empty arguments', function () {
521
expect(function () {
@@ -9,28 +25,117 @@ describe('StackTrace', function () {
925
});
1026

1127
describe('#get', function () {
12-
var unit = new StackTrace();
1328
it('gets stacktrace from current location', function () {
14-
var stackFrames = unit.get().filter(function (stackFrame) {
15-
return stackFrame.getFileName().indexOf('stacktrace-spec.js') > -1;
29+
runs(function () {
30+
new StackTrace().get().then(callback, errback);
31+
});
32+
waits(100);
33+
runs(function () {
34+
expect(callback).toHaveBeenCalled();
35+
expect(callback.mostRecentCall.args[0][0].fileName).toMatch(/stacktrace\-spec\.js\b/);
36+
expect(errback).not.toHaveBeenCalled();
1637
});
17-
expect(stackFrames.length).toEqual(1);
1838
});
1939
});
2040

2141
describe('#fromError', function () {
22-
var unit = new StackTrace();
42+
it('rejects with Error given non-Error object', function () {
43+
runs(function () {
44+
new StackTrace().fromError('BOGUS').then(callback, errback);
45+
});
46+
waits(100);
47+
runs(function () {
48+
expect(callback).not.toHaveBeenCalled();
49+
expect(errback).toHaveBeenCalled();
50+
});
51+
});
52+
2353
it('parses stacktrace from given Error object', function () {
24-
var err;
25-
try {
26-
throw new Error('Yikes!');
27-
} catch (e) {
28-
err = e;
29-
}
30-
var stackFrames = unit.fromError(err).filter(function (stackFrame) {
31-
return stackFrame.getFileName().indexOf('stacktrace-spec.js') > -1;
54+
runs(function () {
55+
try {
56+
throw new Error('Yikes!');
57+
} catch (e) {
58+
new StackTrace().fromError(e).then(callback, errback);
59+
}
60+
});
61+
waits(100);
62+
runs(function () {
63+
expect(callback).toHaveBeenCalled();
64+
expect(errback).not.toHaveBeenCalled();
65+
});
66+
});
67+
68+
it('totally extracts function names', function () {
69+
var TEST_FUNCTION = function () {
70+
try {
71+
throw new Error('Yikes!');
72+
} catch (e) {
73+
function onlySpecSourcesPlease(stackFrame) {
74+
return (stackFrame.fileName || '').indexOf('stacktrace-spec.js') !== -1;
75+
}
76+
77+
new StackTrace().fromError(e, {filter: onlySpecSourcesPlease})
78+
.then(callback, errback);
79+
}
80+
};
81+
runs(TEST_FUNCTION);
82+
waits(100);
83+
runs(function () {
84+
expect(callback).toHaveBeenCalled();
85+
var stackFrames = callback.mostRecentCall.args[0];
86+
expect(stackFrames.length).toEqual(1);
87+
expect(stackFrames[0].fileName).toMatch(/stacktrace\-spec\.js\b/);
88+
expect(stackFrames[0].functionName).toEqual('TEST_FUNCTION');
89+
expect(errback).not.toHaveBeenCalled();
90+
});
91+
});
92+
93+
xit('uses source maps to enhance stack frames', function () {
94+
95+
});
96+
});
97+
98+
describe('#getMappedLocation', function() {
99+
var server;
100+
beforeEach(function () {
101+
server = sinon.fakeServer.create();
102+
});
103+
afterEach(function () {
104+
server.restore();
105+
});
106+
107+
it('defaults to given stackframe if source map location not found', function() {
108+
runs(function() {
109+
var stackframe = new StackFrame(undefined, [], 'http://localhost:9999/test.min.js', 1, 32);
110+
new StackTrace().getMappedLocation(stackframe).then(callback, errback);
111+
server.requests[0].respond(404, {}, '');
112+
});
113+
waits(100);
114+
runs(function() {
115+
expect(callback).toHaveBeenCalled();
116+
expect(callback.mostRecentCall.args[0]).toMatchStackFrame([undefined, [], 'http://localhost:9999/test.min.js', 1, 32]);
117+
expect(errback).not.toHaveBeenCalled();
118+
});
119+
});
120+
121+
it('uses source maps to enhance stack frames', function () {
122+
runs(function() {
123+
var stackframe = new StackFrame(undefined, [], 'http://localhost:9999/test.min.js', 1, 32);
124+
new StackTrace().getMappedLocation(stackframe).then(callback, errback);
125+
var source = 'var foo=function(){};function bar(){}var baz=eval("XXX");\n//@ sourceMappingURL=test.js.map';
126+
server.requests[0].respond(200, { 'Content-Type': 'application/x-javascript' }, source);
127+
});
128+
waits(100);
129+
runs(function() {
130+
var sourceMap = '{"version":3,"sources":["./test.js"],"names":["foo","bar","baz","eval"],"mappings":"AAAA,GAAIA,KAAM,YACV,SAASC,QACT,GAAIC,KAAMC,KAAK","file":"test.min.js"}';
131+
server.requests[1].respond(200, { 'Content-Type': 'application/json' }, sourceMap);
132+
});
133+
waits(100);
134+
runs(function() {
135+
expect(callback).toHaveBeenCalled();
136+
expect(callback.mostRecentCall.args[0]).toMatchStackFrame(['bar', [], './test.js', 2, 9]);
137+
expect(errback).not.toHaveBeenCalled();
32138
});
33-
expect(stackFrames.length).toEqual(1);
34139
});
35140
});
36141

0 commit comments

Comments
 (0)