-
Notifications
You must be signed in to change notification settings - Fork 222
/
source-map-support.js
148 lines (134 loc) · 5.12 KB
/
source-map-support.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
var SourceMapConsumer = require('source-map').SourceMapConsumer;
var path = require('path');
var fs = require('fs');
function mapSourcePosition(cache, position) {
var sourceMap = cache[position.source];
if (!sourceMap && fs.existsSync(position.source)) {
// Get the URL of the source map
var fileData = fs.readFileSync(position.source, 'utf8');
var match = /\/\/@\s*sourceMappingURL=(.*)\s*$/m.exec(fileData);
if (!match) return position;
var sourceMappingURL = match[1];
// Support source map URLs relative to the source URL
var dir = path.dirname(position.source);
sourceMappingURL = path.resolve(dir, sourceMappingURL);
// Parse the source map
if (fs.existsSync(sourceMappingURL)) {
var sourceMapData = fs.readFileSync(sourceMappingURL, 'utf8');
try {
sourceMap = new SourceMapConsumer(sourceMapData);
cache[position.source] = sourceMap;
} catch (e) {
}
}
}
return sourceMap ? sourceMap.originalPositionFor(position) : position;
}
// Parses code generated by FormatEvalOrigin(), a function inside V8:
// https://code.google.com/p/v8/source/browse/trunk/src/messages.js
function mapEvalOrigin(cache, origin) {
// Most eval() calls are in this format
var match = /^eval at ([^(]+) \((.+):(\d+):(\d+)\)$/.exec(origin);
if (match) {
var position = mapSourcePosition(cache, {
source: match[2],
line: match[3],
column: match[4]
});
return 'eval at ' + match[1] + ' (' + position.source + ':' +
position.line + ':' + position.column + ')';
}
// Parse nested eval() calls using recursion
match = /^eval at ([^(]+) \((.+)\)$/.exec(origin);
if (match) {
return 'eval at ' + match[1] + ' (' + mapEvalOrigin(cache, match[2]) + ')';
}
// Make sure we still return useful information if we didn't find anything
return origin;
}
function wrapCallSite(cache, frame) {
// Most call sites will return the source file from getFileName(), but code
// passed to eval() ending in "//@ sourceURL=..." will return the source file
// from getScriptNameOrSourceURL() instead
var source = frame.getFileName() || frame.getScriptNameOrSourceURL();
if (source) {
var position = mapSourcePosition(cache, {
source: source,
line: frame.getLineNumber(),
column: frame.getColumnNumber()
});
return {
__proto__: frame,
getFileName: function() { return position.source; },
getLineNumber: function() { return position.line; },
getColumnNumber: function() { return position.column; },
getScriptNameOrSourceURL: function() { return position.source; }
};
}
// Code called using eval() needs special handling
var origin = frame.getEvalOrigin();
if (origin) {
origin = mapEvalOrigin(cache, origin);
return {
__proto__: frame,
getEvalOrigin: function() { return origin; }
};
}
// If we get here then we were unable to change the source position
return frame;
}
// This function is part of the V8 stack trace API, for more info see:
// http://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
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)
var cache = {};
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
function handleUncaughtExceptions(error) {
if (!error || !error.stack) {
console.log('Uncaught exception:', error);
process.exit();
}
var match = /\n at [^(]+ \((.*):(\d+):(\d+)\)/.exec(error.stack);
if (match) {
var cache = {};
var position = mapSourcePosition(cache, {
source: match[1],
line: match[2],
column: match[3]
});
if (fs.existsSync(position.source)) {
var contents = fs.readFileSync(position.source, 'utf8');
var line = contents.split(/(?:\r\n|\r|\n)/)[position.line - 1];
if (line) {
console.log('\n' + position.source + ':' + position.line);
console.log(line);
console.log(new Array(+position.column).join(' ') + '^');
}
}
}
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);
}
};