Completed
Push — master ( e5e218...aa5bbe )
by Vitaly
01:15
created

stacktracey.js (3 issues)

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

This checks looks for references to variables that have not been declared. This is most likey a typographical error or a variable has been renamed.

To learn more about declaring variables in Javascript, see the MDN.

Loading history...
265
266
/*  ------------------------------------------------------------------------ */
267
268
module.exports = StackTracey
269
270
/*  ------------------------------------------------------------------------ */
271
272