option to not install uncaught exception handler, fix for issue #3

This commit is contained in:
Evan Wallace 2013-03-05 11:36:36 -08:00
parent 9484e8ec05
commit 18992a7be6
4 changed files with 125 additions and 21 deletions

View File

@ -14,7 +14,7 @@ The following terminal commands show a stack trace in node with CoffeeScript fil
$ cat > demo.coffee
require 'source-map-support'
require('source-map-support').install()
foo = ->
bar = -> throw new Error 'this is a demo'
bar()
@ -52,7 +52,7 @@ The following terminal commands show a stack trace in node with TypeScript filen
$ cat > demo.ts
declare function require(name: string);
require('source-map-support');
require('source-map-support').install();
class Foo {
constructor() { this.bar(); }
bar() { throw new Error('this is a demo'); }
@ -77,6 +77,14 @@ The following terminal commands show a stack trace in node with TypeScript filen
at Module.runMain (module.js:492:10)
at process.startup.processNextTick.process._tickCallback (node.js:244:9)
### Options
This module installs two things: a change to the `stack` property on `Error` objects and a handler for uncaught exceptions that mimics node's default exception handler (the handler can be seen in the demos above). You may want to disable the handler if you have your own uncaught exception handler. This can be done by passing an argument to the installer:
require('source-map-support').install({
handleUncaughtExceptions: false
});
### License
This code is available under the [MIT license](http://opensource.org/licenses/MIT).

View File

@ -1,7 +1,7 @@
{
"name": "source-map-support",
"description": "Fixes stack traces for files with source maps",
"version": "0.1.1",
"version": "0.1.2",
"main": "./source-map-support.js",
"scripts": {
"test": "node_modules/mocha/bin/mocha"

View File

@ -89,7 +89,7 @@ function wrapCallSite(cache, frame) {
// This function is part of the V8 stack trace API, for more info see:
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
Error.prepareStackTrace = function(error, stack) {
function prepareStackTrace(error, stack) {
// Store source maps in a cache so we don't load them more than once when
// formatting a single stack trace (don't cache them forever though in case
// the files change on disk and the user wants to see the updated mapping)
@ -97,10 +97,10 @@ Error.prepareStackTrace = function(error, stack) {
return error + stack.map(function(frame) {
return '\n at ' + wrapCallSite(cache, frame);
}).join('');
};
}
// Mimic node's stack trace printing when an exception escapes the process
process.on('uncaughtException', function(error) {
function handleUncaughtExceptions(error) {
if (!error || !error.stack) {
console.log('Uncaught exception:', error);
process.exit();
@ -125,4 +125,24 @@ process.on('uncaughtException', function(error) {
}
console.log(error.stack);
process.exit();
});
}
exports.install = function(options) {
Error.prepareStackTrace = prepareStackTrace;
// Configure options
options = options || {};
var installHandler = 'handleUncaughtExceptions' in options ?
options.handleUncaughtExceptions : true;
// Provide the option to not install the uncaught exception handler. This is
// to support other uncaught exception handlers (in test frameworks, for
// example). If this handler is not installed and there are no other uncaught
// exception handlers, uncaught exceptions will be caught by node's built-in
// exception handler and the process will still be terminated. However, the
// generated JavaScript code will be shown above the stack trace instead of
// the original source code.
if (installHandler) {
process.on('uncaughtException', handleUncaughtExceptions);
}
};

104
test.js
View File

@ -1,23 +1,22 @@
require('./source-map-support');
require('./source-map-support').install();
var SourceMapGenerator = require('source-map').SourceMapGenerator;
var child_process = require('child_process');
var assert = require('assert');
var fs = require('fs');
// Create a source map
var sourceMap = new SourceMapGenerator({
file: '.generated.js',
sourceRoot: '.'
});
for (var i = 1; i <= 100; i++) {
sourceMap.addMapping({
generated: { line: i, column: 1 },
original: { line: 1000 + i, column: 100 + i },
source: 'line' + i + '.js'
});
}
function compareStackTrace(source, expected) {
var sourceMap = new SourceMapGenerator({
file: '.generated.js',
sourceRoot: '.'
});
for (var i = 1; i <= 100; i++) {
sourceMap.addMapping({
generated: { line: i, column: 1 },
original: { line: 1000 + i, column: 100 + i },
source: 'line' + i + '.js'
});
}
fs.writeFileSync('.generated.js.map', sourceMap);
fs.writeFileSync('.generated.js', 'exports.test = function() {' +
source.join('\n') + '};//@ sourceMappingURL=.generated.js.map');
@ -32,6 +31,34 @@ function compareStackTrace(source, expected) {
fs.unlinkSync('.generated.js.map');
}
function compareStdout(done, source, expected) {
var sourceMap = new SourceMapGenerator({
file: '.generated.js',
sourceRoot: '.'
});
sourceMap.addMapping({
generated: { line: 1, column: 1 },
original: { line: 1, column: 1 },
source: '.original.js'
});
fs.writeFileSync('.original.js', 'this is the original code');
fs.writeFileSync('.generated.js.map', sourceMap);
fs.writeFileSync('.generated.js', source.join('\n') +
'//@ sourceMappingURL=.generated.js.map');
child_process.exec('node ./.generated', function(error, stdout, stderr) {
expected = expected.join('\n');
try {
assert.equal((stdout + stderr).slice(0, expected.length), expected);
} catch (e) {
return done(e);
}
fs.unlinkSync('.generated.js');
fs.unlinkSync('.generated.js.map');
fs.unlinkSync('.original.js');
done();
});
}
it('normal throw', function() {
compareStackTrace([
'throw new Error("test");'
@ -126,3 +153,52 @@ it('eval with sourceURL inside eval', function() {
' at Object.exports.test (./line1.js:1001:101)'
]);
});
it('default options', function(done) {
compareStdout(done, [
'',
'function foo() { throw new Error("this is the error"); }',
'require("./source-map-support").install();',
'process.nextTick(foo);',
'process.nextTick(function() { process.exit(1); });'
], [
'',
'./.original.js:1',
'this is the original code',
'^',
'Error: this is the error',
' at foo (./.original.js:1:1)'
]);
});
it('handleUncaughtExceptions is true', function(done) {
compareStdout(done, [
'',
'function foo() { throw new Error("this is the error"); }',
'require("./source-map-support").install({ handleUncaughtExceptions: true });',
'process.nextTick(foo);'
], [
'',
'./.original.js:1',
'this is the original code',
'^',
'Error: this is the error',
' at foo (./.original.js:1:1)'
]);
});
it('handleUncaughtExceptions is false', function(done) {
compareStdout(done, [
'',
'function foo() { throw new Error("this is the error"); }',
'require("./source-map-support").install({ handleUncaughtExceptions: false });',
'process.nextTick(foo);'
], [
'',
__dirname + '/.generated.js:2',
'function foo() { throw new Error("this is the error"); }',
' ^',
'Error: this is the error',
' at foo (./.original.js:1:1)'
]);
});