@zkochan/pnpm
Version:
Fast, disk space efficient package manager
296 lines (212 loc) • 9.85 kB
JavaScript
;
/* ------------------------------------------------------------------------ */
const O = Object,
isBrowser = (typeof window !== 'undefined') && (window.window === window) && window.navigator,
lastOf = x => x[x.length - 1],
getSource = require ('get-source'),
partition = require ('./impl/partition'),
asTable = require ('as-table'),
nixSlashes = x => x.replace (/\\/g, '/'),
pathRoot = isBrowser ? window.location.href : (nixSlashes (process.cwd ()) + '/')
/* ------------------------------------------------------------------------ */
class StackTracey extends Array {
constructor (input, offset) {
const originalInput = input
, isParseableSyntaxError = input && (input instanceof SyntaxError && !isBrowser)
super ()
/* Fixes for Safari */
this.constructor = StackTracey
this.__proto__ = StackTracey.prototype
/* new StackTracey () */
if (!input) {
input = new Error ()
offset = (offset === undefined) ? 1 : offset
}
/* new StackTracey (Error) */
if (input instanceof Error) {
input = input[StackTracey.stack] || input.stack || ''
}
/* new StackTracey (string) */
if (typeof input === 'string') {
input = StackTracey.rawParse (input).slice (offset).map (StackTracey.extractEntryMetadata)
}
/* new StackTracey (array) */
if (Array.isArray (input)) {
if (isParseableSyntaxError) {
const rawLines = module.require ('util').inspect (originalInput).split ('\n')
, fileLine = rawLines[0].split (':')
, line = fileLine.pop ()
, file = fileLine.join (':')
if (file) {
input.unshift ({
file: nixSlashes (file),
line: line,
column: (rawLines[2] || '').indexOf ('^') + 1,
sourceLine: rawLines[1],
callee: '(syntax error)',
syntaxError: true
})
}
}
this.length = input.length
input.forEach ((x, i) => this[i] = x)
}
}
static extractEntryMetadata (e) {
const fileRelative = StackTracey.relativePath (e.file || '')
return O.assign (e, {
calleeShort: e.calleeShort || lastOf ((e.callee || '').split ('.')),
fileRelative: fileRelative,
fileShort: StackTracey.shortenPath (fileRelative),
fileName: lastOf ((e.file || '').split ('/')),
thirdParty: StackTracey.isThirdParty (fileRelative) && !e.index
})
}
static shortenPath (relativePath) {
return relativePath.replace (/^node_modules\//, '')
.replace (/^webpack\/bootstrap\//, '')
}
static relativePath (fullPath) {
return fullPath.replace (pathRoot, '')
.replace (/^.*\:\/\/?\/?/, '')
}
static isThirdParty (relativePath) {
return (relativePath[0] === '~') || // webpack-specific heuristic
(relativePath[0] === '/') || // external source
(relativePath.indexOf ('node_modules') === 0) ||
(relativePath.indexOf ('webpack/bootstrap') === 0)
}
static rawParse (str) {
const lines = (str || '').split ('\n')
const entries = lines.map (line => { line = line.trim ()
var callee, fileLineColumn = [], native, planA, planB
if ((planA = line.match (/at (.+) \((.+)\)/)) ||
(planA = line.match (/(.*)@(.*)/))) {
callee = planA[1]
native = (planA[2] === 'native')
fileLineColumn = (planA[2].match (/(.*):(.+):(.+)/) || []).slice (1) }
else if ((planB = line.match (/^(at\s+)*(.+):([0-9]+):([0-9]+)/) )) {
fileLineColumn = (planB).slice (2) }
else {
return undefined }
/* Detect things like Array.reduce
TODO: detect more built-in types */
if (callee && !fileLineColumn[0]) {
const type = callee.split ('.')[0]
if (type === 'Array') {
native = true
}
}
return {
beforeParse: line,
callee: callee || '',
index: isBrowser && (fileLineColumn[0] === window.location.href),
native: native || false,
file: nixSlashes (fileLineColumn[0] || ''),
line: parseInt (fileLineColumn[1] || '', 10) || undefined,
column: parseInt (fileLineColumn[2] || '', 10) || undefined } })
return entries.filter (x => (x !== undefined))
}
withSource (i) {
return this[i] && StackTracey.withSource (this[i])
}
static withSource (loc) {
if (loc.sourceFile || (loc.file && loc.file.indexOf ('<') >= 0)) { // skip things like <anonymous> and stuff that was already fetched
return loc
} else {
let resolved = getSource (loc.file || '').resolve (loc)
if (!resolved.sourceFile) {
return loc
}
if (!resolved.sourceFile.error) {
resolved.file = nixSlashes (resolved.sourceFile.path)
resolved = StackTracey.extractEntryMetadata (resolved)
}
if (!resolved.sourceLine.error && resolved.sourceLine.includes ('// @hide')) {
resolved.sourceLine = resolved.sourceLine.replace ('// @hide', '')
resolved.hide = true
}
return O.assign ({ sourceLine: '' }, loc, resolved)
}
}
get withSources () {
return new StackTracey (this.map (StackTracey.withSource))
}
get mergeRepeatedLines () {
return new StackTracey (
partition (this, e => e.file + e.line).map (
group => {
return group.items.slice (1).reduce ((memo, entry) => {
memo.callee = (memo.callee || '<anonymous>') + ' → ' + (entry.callee || '<anonymous>')
memo.calleeShort = (memo.calleeShort || '<anonymous>') + ' → ' + (entry.calleeShort || '<anonymous>')
return memo }, O.assign ({}, group.items[0])) }))
}
get clean () {
return this.withSources.mergeRepeatedLines.filter ((e, i) => (i === 0) || !(e.thirdParty || e.hide || e.native))
}
at (i) {
return O.assign ({
beforeParse: '',
callee: '<???>',
index: false,
native: false,
file: '<???>',
line: 0,
column: 0
}, this[i])
}
static locationsEqual (a, b) {
return (a.file === b.file) &&
(a.line === b.line) &&
(a.column === b.column)
}
get pretty () {
const trimEnd = (s, n) => s && ((s.length > n) ? (s.slice (0, n-1) + '…') : s)
const trimStart = (s, n) => s && ((s.length > n) ? ('…' + s.slice (-(n-1))) : s)
return asTable (this.withSources.map (
e => [
('at ' + trimEnd (e.calleeShort, StackTracey.maxColumnWidths.callee)),
trimStart ((e.fileShort && (e.fileShort + ':' + e.line)) || '', StackTracey.maxColumnWidths.file),
trimEnd (((e.sourceLine || '').trim () || ''), StackTracey.maxColumnWidths.sourceLine)
]))
}
static resetCache () {
getSource.resetCache ()
}
}
/* Some default configuration options
------------------------------------------------------------------------ */
StackTracey.maxColumnWidths = {
callee: 30,
file: 40,
sourceLine: 80
}
/* Chaining helper for .isThirdParty
------------------------------------------------------------------------ */
;(() => {
const methods = {
include (pred) {
const f = StackTracey.isThirdParty
O.assign (StackTracey.isThirdParty = (path => f (path) || pred (path)), methods)
},
except (pred) {
const f = StackTracey.isThirdParty
O.assign (StackTracey.isThirdParty = (path => f (path) && !pred (path)), methods)
},
}
O.assign (StackTracey.isThirdParty, methods)
}) ()
/* Array methods
------------------------------------------------------------------------ */
;['map', 'filter', 'slice', 'concat', 'reverse'].forEach (name => {
StackTracey.prototype[name] = function (/*...args */) { // no support for ...args in Node v4 :(
const arr = Array.from (this)
return new StackTracey (arr[name].apply (arr, arguments))
}
})
/* A private field that an Error instance can expose
------------------------------------------------------------------------ */
StackTracey.stack = /* istanbul ignore next */ (typeof Symbol !== 'undefined') ? Symbol.for ('StackTracey') : '__StackTracey'
/* ------------------------------------------------------------------------ */
module.exports = StackTracey
/* ------------------------------------------------------------------------ */