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