stacktracey.js   A
last analyzed

Size

Lines of Code 282

Duplication

Duplicated Lines 0
Ratio 0 %

Importance

Changes 8
Bugs 2 Features 3
Metric Value
c 8
b 2
f 3
nc 8
dl 0
loc 282
rs 10
noi 3

1 Function

Rating   Name   Duplication   Size   Complexity  
A ➔ ??? 0 1 1
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
      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
0 ignored issues
show
Unused Code introduced by
The assignment to variable fileLineColumn seems to be never used. Consider removing it.
Loading history...
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'
0 ignored issues
show
Bug introduced by
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...
279
280
/*  ------------------------------------------------------------------------ */
281
282
module.exports = StackTracey
283
284
/*  ------------------------------------------------------------------------ */
285
286