1
|
|
|
"use strict"; |
2
|
|
|
|
3
|
|
|
/* ------------------------------------------------------------------------ */ |
4
|
|
|
|
5
|
|
|
const O = Object, |
|
|
|
|
6
|
|
|
isBrowser = (typeof window !== 'undefined') && (window.window === window) && window.navigator, |
7
|
|
|
lastOf = x => x[x.length - 1], |
8
|
|
|
getSource = require ('get-source'), |
9
|
|
|
partition = require ('./impl/partition'), |
10
|
|
|
asTable = require ('as-table'), |
11
|
|
|
nixSlashes = x => x.replace (/\\/g, '/'), |
12
|
|
|
pathRoot = isBrowser ? window.location.href : (nixSlashes (process.cwd ()) + '/') |
13
|
|
|
|
14
|
|
|
/* ------------------------------------------------------------------------ */ |
15
|
|
|
|
16
|
|
|
class StackTracey extends Array { |
17
|
|
|
|
18
|
|
|
constructor (input, offset) { |
19
|
|
|
|
20
|
|
|
const originalInput = input |
21
|
|
|
, isParseableSyntaxError = input && (input instanceof SyntaxError && !isBrowser) |
22
|
|
|
|
23
|
|
|
super () |
24
|
|
|
|
25
|
|
|
/* Fixes for Safari */ |
26
|
|
|
|
27
|
|
|
this.constructor = StackTracey |
28
|
|
|
this.__proto__ = StackTracey.prototype |
29
|
|
|
|
30
|
|
|
/* new StackTracey () */ |
31
|
|
|
|
32
|
|
|
if (!input) { |
33
|
|
|
input = new Error () |
34
|
|
|
offset = (offset === undefined) ? 1 : offset |
35
|
|
|
} |
36
|
|
|
|
37
|
|
|
/* new StackTracey (Error) */ |
38
|
|
|
|
39
|
|
|
if (input instanceof Error) { |
40
|
|
|
input = input[StackTracey.stack] || input.stack || '' |
41
|
|
|
} |
42
|
|
|
|
43
|
|
|
/* new StackTracey (string) */ |
44
|
|
|
|
45
|
|
|
if (typeof input === 'string') { |
46
|
|
|
input = StackTracey.rawParse (input).slice (offset).map (StackTracey.extractEntryMetadata) |
47
|
|
|
} |
48
|
|
|
|
49
|
|
|
/* new StackTracey (array) */ |
50
|
|
|
|
51
|
|
|
if (Array.isArray (input)) { |
52
|
|
|
|
53
|
|
|
if (isParseableSyntaxError) { |
54
|
|
|
|
55
|
|
|
const rawLines = module.require ('util').inspect (originalInput).split ('\n') |
56
|
|
|
, fileLine = rawLines[0].split (':') |
57
|
|
|
, line = fileLine.pop () |
58
|
|
|
, file = fileLine.join (':') |
59
|
|
|
|
60
|
|
|
if (file) { |
61
|
|
|
input.unshift ({ |
62
|
|
|
file: nixSlashes (file), |
63
|
|
|
line: line, |
64
|
|
|
column: (rawLines[2] || '').indexOf ('^') + 1, |
65
|
|
|
sourceLine: rawLines[1], |
66
|
|
|
callee: '(syntax error)', |
67
|
|
|
syntaxError: true |
68
|
|
|
}) |
69
|
|
|
} |
70
|
|
|
} |
71
|
|
|
|
72
|
|
|
this.length = input.length |
73
|
|
|
input.forEach ((x, i) => this[i] = x) |
74
|
|
|
} |
75
|
|
|
} |
76
|
|
|
|
77
|
|
|
static extractEntryMetadata (e) { |
78
|
|
|
|
79
|
|
|
const fileRelative = StackTracey.relativePath (e.file || '') |
80
|
|
|
|
81
|
|
|
return O.assign (e, { |
82
|
|
|
|
83
|
|
|
calleeShort: e.calleeShort || lastOf ((e.callee || '').split ('.')), |
84
|
|
|
fileRelative: fileRelative, |
85
|
|
|
fileShort: StackTracey.shortenPath (fileRelative), |
86
|
|
|
fileName: lastOf ((e.file || '').split ('/')), |
87
|
|
|
thirdParty: StackTracey.isThirdParty (fileRelative) && !e.index |
88
|
|
|
}) |
89
|
|
|
} |
90
|
|
|
|
91
|
|
|
static shortenPath (relativePath) { |
92
|
|
|
return relativePath.replace (/^node_modules\//, '') |
93
|
|
|
.replace (/^webpack\/bootstrap\//, '') |
94
|
|
|
} |
95
|
|
|
|
96
|
|
|
static relativePath (fullPath) { |
97
|
|
|
return fullPath.replace (pathRoot, '') |
98
|
|
|
.replace (/^.*\:\/\/?\/?/, '') |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
static isThirdParty (relativePath) { |
102
|
|
|
return (relativePath[0] === '~') || // webpack-specific heuristic |
103
|
|
|
(relativePath[0] === '/') || // external source |
104
|
|
|
(relativePath.indexOf ('node_modules') === 0) || |
105
|
|
|
(relativePath.indexOf ('webpack/bootstrap') === 0) |
106
|
|
|
} |
107
|
|
|
|
108
|
|
|
static rawParse (str) { |
109
|
|
|
|
110
|
|
|
const lines = (str || '').split ('\n') |
111
|
|
|
|
112
|
|
|
const entries = lines.map (line => { line = line.trim () |
113
|
|
|
|
114
|
|
|
var callee, fileLineColumn = [], native, planA, planB |
|
|
|
|
115
|
|
|
|
116
|
|
|
if ((planA = line.match (/at (.+) \((.+)\)/)) || |
117
|
|
|
(planA = line.match (/(.*)@(.*)/))) { |
118
|
|
|
|
119
|
|
|
callee = planA[1] |
120
|
|
|
native = (planA[2] === 'native') |
121
|
|
|
fileLineColumn = (planA[2].match (/(.*):(.+):(.+)/) || []).slice (1) } |
122
|
|
|
|
123
|
|
|
else if ((planB = line.match (/^(at\s+)*(.+):([0-9]+):([0-9]+)/) )) { |
124
|
|
|
fileLineColumn = (planB).slice (2) } |
125
|
|
|
|
126
|
|
|
else { |
127
|
|
|
return undefined } |
128
|
|
|
|
129
|
|
|
/* Detect things like Array.reduce |
130
|
|
|
TODO: detect more built-in types */ |
131
|
|
|
|
132
|
|
|
if (callee && !fileLineColumn[0]) { |
133
|
|
|
const type = callee.split ('.')[0] |
134
|
|
|
if (type === 'Array') { |
135
|
|
|
native = true |
136
|
|
|
} |
137
|
|
|
} |
138
|
|
|
|
139
|
|
|
return { |
140
|
|
|
beforeParse: line, |
141
|
|
|
callee: callee || '', |
142
|
|
|
index: isBrowser && (fileLineColumn[0] === window.location.href), |
143
|
|
|
native: native || false, |
144
|
|
|
file: nixSlashes (fileLineColumn[0] || ''), |
145
|
|
|
line: parseInt (fileLineColumn[1] || '', 10) || undefined, |
146
|
|
|
column: parseInt (fileLineColumn[2] || '', 10) || undefined } }) |
147
|
|
|
|
148
|
|
|
return entries.filter (x => (x !== undefined)) |
149
|
|
|
} |
150
|
|
|
|
151
|
|
|
withSource (i) { |
152
|
|
|
return this[i] && StackTracey.withSource (this[i]) |
153
|
|
|
} |
154
|
|
|
|
155
|
|
|
static withSource (loc) { |
156
|
|
|
|
157
|
|
|
if (loc.sourceFile || (loc.file && loc.file.indexOf ('<') >= 0)) { // skip things like <anonymous> and stuff that was already fetched |
158
|
|
|
return loc |
159
|
|
|
|
160
|
|
|
} else { |
161
|
|
|
|
162
|
|
|
let resolved = getSource (loc.file || '').resolve (loc) |
163
|
|
|
|
164
|
|
|
if (!resolved.sourceFile) { |
165
|
|
|
return loc |
166
|
|
|
} |
167
|
|
|
|
168
|
|
|
if (!resolved.sourceFile.error) { |
169
|
|
|
resolved.file = nixSlashes (resolved.sourceFile.path) |
170
|
|
|
resolved = StackTracey.extractEntryMetadata (resolved) |
171
|
|
|
} |
172
|
|
|
|
173
|
|
|
if (!resolved.sourceLine.error && resolved.sourceLine.includes ('// @hide')) { |
174
|
|
|
resolved.sourceLine = resolved.sourceLine.replace ('// @hide', '') |
175
|
|
|
resolved.hide = true |
176
|
|
|
} |
177
|
|
|
|
178
|
|
|
return O.assign ({ sourceLine: '' }, loc, resolved) |
179
|
|
|
} |
180
|
|
|
} |
181
|
|
|
|
182
|
|
|
get withSources () { |
183
|
|
|
return new StackTracey (this.map (StackTracey.withSource)) |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
get mergeRepeatedLines () { |
187
|
|
|
return new StackTracey ( |
188
|
|
|
partition (this, e => e.file + e.line).map ( |
189
|
|
|
group => { |
190
|
|
|
return group.items.slice (1).reduce ((memo, entry) => { |
191
|
|
|
memo.callee = (memo.callee || '<anonymous>') + ' → ' + (entry.callee || '<anonymous>') |
192
|
|
|
memo.calleeShort = (memo.calleeShort || '<anonymous>') + ' → ' + (entry.calleeShort || '<anonymous>') |
193
|
|
|
return memo }, O.assign ({}, group.items[0])) })) |
194
|
|
|
} |
195
|
|
|
|
196
|
|
|
get clean () { |
197
|
|
|
return this.withSources.mergeRepeatedLines.filter ((e, i) => (i === 0) || !(e.thirdParty || e.hide || e.native)) |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
at (i) { |
201
|
|
|
return O.assign ({ |
202
|
|
|
|
203
|
|
|
beforeParse: '', |
204
|
|
|
callee: '<???>', |
205
|
|
|
index: false, |
206
|
|
|
native: false, |
207
|
|
|
file: '<???>', |
208
|
|
|
line: 0, |
209
|
|
|
column: 0 |
210
|
|
|
|
211
|
|
|
}, this[i]) |
212
|
|
|
} |
213
|
|
|
|
214
|
|
|
static locationsEqual (a, b) { |
215
|
|
|
return (a.file === b.file) && |
216
|
|
|
(a.line === b.line) && |
217
|
|
|
(a.column === b.column) |
218
|
|
|
} |
219
|
|
|
|
220
|
|
|
get pretty () { |
221
|
|
|
|
222
|
|
|
const trimEnd = (s, n) => s && ((s.length > n) ? (s.slice (0, n-1) + '…') : s) |
223
|
|
|
const trimStart = (s, n) => s && ((s.length > n) ? ('…' + s.slice (-(n-1))) : s) |
224
|
|
|
|
225
|
|
|
return asTable (this.withSources.map ( |
226
|
|
|
e => [ |
227
|
|
|
('at ' + trimEnd (e.calleeShort, 30)), |
228
|
|
|
trimStart ((e.fileShort && (e.fileShort + ':' + e.line)) || '', 40), |
229
|
|
|
trimEnd (((e.sourceLine || '').trim () || ''), 80) |
230
|
|
|
])) |
231
|
|
|
} |
232
|
|
|
|
233
|
|
|
static resetCache () { |
234
|
|
|
|
235
|
|
|
getSource.resetCache () |
236
|
|
|
} |
237
|
|
|
} |
238
|
|
|
|
239
|
|
|
/* Chaining helper for .isThirdParty |
240
|
|
|
------------------------------------------------------------------------ */ |
241
|
|
|
|
242
|
|
|
(() => { |
243
|
|
|
|
244
|
|
|
const methods = { |
245
|
|
|
|
246
|
|
|
include (pred) { |
247
|
|
|
|
248
|
|
|
const f = StackTracey.isThirdParty |
249
|
|
|
O.assign (StackTracey.isThirdParty = (path => f (path) || pred (path)), methods) |
250
|
|
|
}, |
251
|
|
|
|
252
|
|
|
except (pred) { |
253
|
|
|
|
254
|
|
|
const f = StackTracey.isThirdParty |
255
|
|
|
O.assign (StackTracey.isThirdParty = (path => f (path) && !pred (path)), methods) |
256
|
|
|
}, |
257
|
|
|
} |
258
|
|
|
|
259
|
|
|
O.assign (StackTracey.isThirdParty, methods) |
260
|
|
|
|
261
|
|
|
}) () |
262
|
|
|
|
263
|
|
|
/* Array methods |
264
|
|
|
------------------------------------------------------------------------ */ |
265
|
|
|
|
266
|
|
|
;['map', 'filter', 'slice', 'concat', 'reverse'].forEach (name => { |
267
|
|
|
|
268
|
|
|
StackTracey.prototype[name] = function (/*...args */) { // no support for ...args in Node v4 :( |
269
|
|
|
|
270
|
|
|
const arr = Array.from (this) |
271
|
|
|
return new StackTracey (arr[name].apply (arr, arguments)) |
272
|
|
|
} |
273
|
|
|
}) |
274
|
|
|
|
275
|
|
|
/* A private field that an Error instance can expose |
276
|
|
|
------------------------------------------------------------------------ */ |
277
|
|
|
|
278
|
|
|
StackTracey.stack = /* istanbul ignore next */ (typeof Symbol !== 'undefined') ? Symbol.for ('StackTracey') : '__StackTracey' |
|
|
|
|
279
|
|
|
|
280
|
|
|
/* ------------------------------------------------------------------------ */ |
281
|
|
|
|
282
|
|
|
module.exports = StackTracey |
283
|
|
|
|
284
|
|
|
/* ------------------------------------------------------------------------ */ |
285
|
|
|
|
286
|
|
|
|