forked from bugsnag/bugsnag-js
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathsurrounding-code.js
118 lines (106 loc) · 3.56 KB
/
surrounding-code.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
const SURROUNDING_LINES = 3
const MAX_LINE_LENGTH = 200
const { createReadStream } = require('fs')
const { Writable } = require('stream')
const pump = require('pump')
const byline = require('byline')
module.exports = {
load: client => {
if (!client._config.sendCode) return
const loadSurroundingCode = (stackframe, cache) => new Promise((resolve, reject) => {
try {
if (!stackframe.lineNumber || !stackframe.file) return resolve(stackframe)
const cacheKey = `${stackframe.file}@${stackframe.lineNumber}`
if (cacheKey in cache) {
stackframe.code = cache[cacheKey]
return resolve(stackframe)
}
getSurroundingCode(stackframe.file, stackframe.lineNumber, (err, code) => {
if (err) return resolve(stackframe)
stackframe.code = cache[cacheKey] = code
return resolve(stackframe)
})
} catch (e) {
return resolve(stackframe)
}
})
client.addOnError(event => new Promise((resolve, reject) => {
const cache = Object.create(null)
const allFrames = event.errors.reduce((accum, er) => accum.concat(er.stacktrace), [])
pMapSeries(allFrames.map(stackframe => () => loadSurroundingCode(stackframe, cache)))
.then(resolve)
.catch(reject)
}))
},
configSchema: {
sendCode: {
defaultValue: () => true,
validate: value => value === true || value === false,
message: 'should be true or false'
}
}
}
const getSurroundingCode = (file, lineNumber, cb) => {
const start = lineNumber - SURROUNDING_LINES
const end = lineNumber + SURROUNDING_LINES
const reader = createReadStream(file, { encoding: 'utf8' })
const splitter = new byline.LineStream({ keepEmptyLines: true })
const slicer = new CodeRange({ start, end })
// if the slicer has enough lines already, no need to keep reading from the file
slicer.on('done', () => reader.destroy())
pump(reader, splitter, slicer, (err) => {
// reader.destroy() causes a "premature close" error which we can tolerate
if (err && err.message !== 'premature close') return cb(err)
cb(null, slicer.getCode())
})
}
// This writable stream takes { start, end } options specifying the
// range of lines that should be extracted from a file. Pipe a readable
// stream to it that provides source lines as each chunk. If the range
// is satisfied before the end of the readable stream, it will emit the
// 'done' event. Once a 'done' or 'finish' event has been seen, call getCode()
// to get the range in the following format:
// {
// '10': 'function getSquare (cb) {',
// '11': ' rectangles.find({',
// '12': ' length: 12',
// '13': ' width: 12',
// '14': ' }, err => cb)',
// '15': '}'
// }
class CodeRange extends Writable {
constructor (opts) {
super({ ...opts, decodeStrings: false })
this._start = opts.start
this._end = opts.end
this._n = 0
this._code = {}
}
_write (chunk, enc, cb) {
this._n++
if (this._n < this._start) return cb(null)
if (this._n <= this._end) {
this._code[String(this._n)] = chunk.length <= MAX_LINE_LENGTH ? chunk : chunk.substr(0, MAX_LINE_LENGTH)
return cb(null)
}
this.emit('done')
return cb(null)
}
getCode () {
return this._code
}
}
const pMapSeries = (ps) => {
return new Promise((resolve, reject) => {
const res = []
ps
.reduce((accum, p) => {
return accum.then(r => {
res.push(r)
return p()
})
}, Promise.resolve())
.then(r => { res.push(r) })
.then(() => { resolve(res.slice(1)) })
})
}