stack-utils
Advanced tools
Comparing version 1.0.2 to 2.0.0
502
index.js
'use strict'; | ||
module.exports = StackUtils; | ||
function StackUtils(opts) { | ||
if (!(this instanceof StackUtils)) { | ||
throw new Error('StackUtils constructor must be called with new'); | ||
} | ||
opts = opts || {}; | ||
this._cwd = (opts.cwd || process.cwd()).replace(/\\/g, '/'); | ||
this._internals = opts.internals || []; | ||
this._wrapCallSite = opts.wrapCallSite || false; | ||
} | ||
const escapeStringRegexp = require('escape-string-regexp'); | ||
module.exports.nodeInternals = nodeInternals; | ||
const natives = [].concat( | ||
require('module').builtinModules, | ||
'bootstrap_node', | ||
'node', | ||
'internal/bootstrap/node' | ||
).map(n => new RegExp(`\\(${n}\\.js:\\d+:\\d+\\)$`)); | ||
function nodeInternals() { | ||
if (!module.exports.natives) { | ||
module.exports.natives = Object.keys(process.binding('natives')); | ||
module.exports.natives.push('bootstrap_node', 'node', | ||
'internal/bootstrap/node'); | ||
} | ||
natives.push( | ||
// XXX are `bootstrap_node.js` and `node.js` needed in supported versions? | ||
/\s*at (bootstrap_)?node\.js:\d+:\d+?$/, | ||
/\(internal\/[^:]+:\d+:\d+\)$/, | ||
/\s*at internal[/]main[/]run_main_module\.js:\d+:\d+$/, | ||
/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/ | ||
); | ||
return module.exports.natives.map(function (n) { | ||
return new RegExp('\\(' + n + '\\.js:\\d+:\\d+\\)$'); | ||
}).concat([ | ||
/\s*at (bootstrap_)?node\.js:\d+:\d+?$/, | ||
/\(internal\/[^:]+:\d+:\d+\)$/, | ||
/\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/ | ||
]); | ||
} | ||
class StackUtils { | ||
constructor (opts) { | ||
opts = { | ||
ignoredPackages: [], | ||
...opts | ||
}; | ||
StackUtils.prototype.clean = function (stack) { | ||
if (!Array.isArray(stack)) { | ||
stack = stack.split('\n'); | ||
if ('internals' in opts === false) { | ||
opts.internals = StackUtils.nodeInternals(); | ||
} | ||
if ('cwd' in opts === false) { | ||
opts.cwd = process.cwd() | ||
} | ||
this._cwd = opts.cwd.replace(/\\/g, '/'); | ||
this._internals = [].concat( | ||
opts.internals, | ||
ignoredPackagesRegExp(opts.ignoredPackages) | ||
); | ||
this._wrapCallSite = opts.wrapCallSite || false; | ||
} | ||
if (!(/^\s*at /.test(stack[0])) && | ||
(/^\s*at /.test(stack[1]))) { | ||
stack = stack.slice(1); | ||
static nodeInternals () { | ||
return [...natives]; | ||
} | ||
var outdent = false; | ||
var lastNonAtLine = null; | ||
var result = []; | ||
clean (stack, indent = 0) { | ||
indent = ' '.repeat(indent); | ||
stack.forEach(function (st) { | ||
st = st.replace(/\\/g, '/'); | ||
var isInternal = this._internals.some(function (internal) { | ||
return internal.test(st); | ||
}); | ||
if (!Array.isArray(stack)) { | ||
stack = stack.split('\n'); | ||
} | ||
if (isInternal) { | ||
return null; | ||
if (!(/^\s*at /.test(stack[0])) && (/^\s*at /.test(stack[1]))) { | ||
stack = stack.slice(1); | ||
} | ||
var isAtLine = /^\s*at /.test(st); | ||
let outdent = false; | ||
let lastNonAtLine = null; | ||
const result = []; | ||
if (outdent) { | ||
st = st.replace(/\s+$/, '').replace(/^(\s+)at /, '$1'); | ||
} else { | ||
st = st.trim(); | ||
if (isAtLine) { | ||
st = st.substring(3); | ||
stack.forEach(st => { | ||
st = st.replace(/\\/g, '/'); | ||
if (this._internals.some(internal => internal.test(st))) { | ||
return; | ||
} | ||
} | ||
st = st.replace(this._cwd + '/', ''); | ||
const isAtLine = /^\s*at /.test(st); | ||
if (st) { | ||
if (isAtLine) { | ||
if (lastNonAtLine) { | ||
result.push(lastNonAtLine); | ||
lastNonAtLine = null; | ||
if (outdent) { | ||
st = st.trimEnd().replace(/^(\s+)at /, '$1'); | ||
} else { | ||
st = st.trim(); | ||
if (isAtLine) { | ||
st = st.slice(3); | ||
} | ||
result.push(st); | ||
} else { | ||
outdent = true; | ||
lastNonAtLine = st; | ||
} | ||
} | ||
}, this); | ||
stack = result.join('\n').trim(); | ||
st = st.replace(`${this._cwd}/`, ''); | ||
if (stack) { | ||
return stack + '\n'; | ||
} | ||
return ''; | ||
}; | ||
if (st) { | ||
if (isAtLine) { | ||
if (lastNonAtLine) { | ||
result.push(lastNonAtLine); | ||
lastNonAtLine = null; | ||
} | ||
StackUtils.prototype.captureString = function (limit, fn) { | ||
if (typeof limit === 'function') { | ||
fn = limit; | ||
limit = Infinity; | ||
} | ||
if (!fn) { | ||
fn = this.captureString; | ||
} | ||
result.push(st); | ||
} else { | ||
outdent = true; | ||
lastNonAtLine = st; | ||
} | ||
} | ||
}); | ||
var limitBefore = Error.stackTraceLimit; | ||
if (limit) { | ||
Error.stackTraceLimit = limit; | ||
return result.map(line => `${indent}${line}\n`).join(''); | ||
} | ||
var obj = {}; | ||
captureString (limit, fn = this.captureString) { | ||
if (typeof limit === 'function') { | ||
fn = limit; | ||
limit = Infinity; | ||
} | ||
Error.captureStackTrace(obj, fn); | ||
var stack = obj.stack; | ||
Error.stackTraceLimit = limitBefore; | ||
const {stackTraceLimit} = Error; | ||
if (limit) { | ||
Error.stackTraceLimit = limit; | ||
} | ||
return this.clean(stack); | ||
}; | ||
const obj = {}; | ||
StackUtils.prototype.capture = function (limit, fn) { | ||
if (typeof limit === 'function') { | ||
fn = limit; | ||
limit = Infinity; | ||
Error.captureStackTrace(obj, fn); | ||
const {stack} = obj; | ||
Error.stackTraceLimit = stackTraceLimit; | ||
return this.clean(stack); | ||
} | ||
if (!fn) { | ||
fn = this.capture; | ||
} | ||
var prepBefore = Error.prepareStackTrace; | ||
var limitBefore = Error.stackTraceLimit; | ||
var wrapCallSite = this._wrapCallSite; | ||
Error.prepareStackTrace = function (obj, site) { | ||
if (wrapCallSite) { | ||
return site.map(wrapCallSite); | ||
capture (limit, fn = this.capture) { | ||
if (typeof limit === 'function') { | ||
fn = limit; | ||
limit = Infinity; | ||
} | ||
return site; | ||
}; | ||
if (limit) { | ||
Error.stackTraceLimit = limit; | ||
} | ||
const {prepareStackTrace, stackTraceLimit} = Error; | ||
Error.prepareStackTrace = (obj, site) => { | ||
if (this._wrapCallSite) { | ||
return site.map(this._wrapCallSite); | ||
} | ||
var obj = {}; | ||
Error.captureStackTrace(obj, fn); | ||
var stack = obj.stack; | ||
Error.prepareStackTrace = prepBefore; | ||
Error.stackTraceLimit = limitBefore; | ||
return site; | ||
}; | ||
return stack; | ||
}; | ||
if (limit) { | ||
Error.stackTraceLimit = limit; | ||
} | ||
StackUtils.prototype.at = function at(fn) { | ||
if (!fn) { | ||
fn = at; | ||
const obj = {}; | ||
Error.captureStackTrace(obj, fn); | ||
const { stack } = obj; | ||
Object.assign(Error, {prepareStackTrace, stackTraceLimit}); | ||
return stack; | ||
} | ||
var site = this.capture(1, fn)[0]; | ||
at (fn = this.at) { | ||
const [site] = this.capture(1, fn); | ||
if (!site) { | ||
return {}; | ||
} | ||
if (!site) { | ||
return {}; | ||
} | ||
var res = { | ||
line: site.getLineNumber(), | ||
column: site.getColumnNumber() | ||
}; | ||
const res = { | ||
line: site.getLineNumber(), | ||
column: site.getColumnNumber() | ||
}; | ||
this._setFile(res, site.getFileName()); | ||
setFile(res, site.getFileName(), this._cwd); | ||
if (site.isConstructor()) { | ||
res.constructor = true; | ||
} | ||
if (site.isConstructor()) { | ||
res.constructor = true; | ||
} | ||
if (site.isEval()) { | ||
res.evalOrigin = site.getEvalOrigin(); | ||
} | ||
if (site.isEval()) { | ||
res.evalOrigin = site.getEvalOrigin(); | ||
} | ||
// Node v10 stopped with the isNative() on callsites, apparently | ||
/* istanbul ignore next */ | ||
if (site.isNative()) { | ||
res.native = true; | ||
} | ||
// Node v10 stopped with the isNative() on callsites, apparently | ||
/* istanbul ignore next */ | ||
if (site.isNative()) { | ||
res.native = true; | ||
} | ||
var typename = null; | ||
try { | ||
typename = site.getTypeName(); | ||
} catch (er) {} | ||
let typename; | ||
try { | ||
typename = site.getTypeName(); | ||
} catch (_) { | ||
} | ||
if (typename && | ||
typename !== 'Object' && | ||
typename !== '[object Object]') { | ||
res.type = typename; | ||
} | ||
if (typename && typename !== 'Object' && typename !== '[object Object]') { | ||
res.type = typename; | ||
} | ||
var fname = site.getFunctionName(); | ||
if (fname) { | ||
res.function = fname; | ||
const fname = site.getFunctionName(); | ||
if (fname) { | ||
res.function = fname; | ||
} | ||
const meth = site.getMethodName(); | ||
if (meth && fname !== meth) { | ||
res.method = meth; | ||
} | ||
return res; | ||
} | ||
var meth = site.getMethodName(); | ||
if (meth && fname !== meth) { | ||
res.method = meth; | ||
parseLine (line) { | ||
const match = line && line.match(re); | ||
if (!match) { | ||
return null; | ||
} | ||
const ctor = match[1] === 'new'; | ||
let fname = match[2]; | ||
const evalOrigin = match[3]; | ||
const evalFile = match[4]; | ||
const evalLine = Number(match[5]); | ||
const evalCol = Number(match[6]); | ||
let file = match[7]; | ||
const lnum = match[8]; | ||
const col = match[9]; | ||
const native = match[10] === 'native'; | ||
const closeParen = match[11] === ')'; | ||
let method; | ||
const res = {}; | ||
if (lnum) { | ||
res.line = Number(lnum); | ||
} | ||
if (col) { | ||
res.column = Number(col); | ||
} | ||
if (closeParen && file) { | ||
// make sure parens are balanced | ||
// if we have a file like "asdf) [as foo] (xyz.js", then odds are | ||
// that the fname should be += " (asdf) [as foo]" and the file | ||
// should be just "xyz.js" | ||
// walk backwards from the end to find the last unbalanced ( | ||
let closes = 0; | ||
for (let i = file.length - 1; i > 0; i--) { | ||
if (file.charAt(i) === ')') { | ||
closes++; | ||
} else if (file.charAt(i) === '(' && file.charAt(i - 1) === ' ') { | ||
closes--; | ||
if (closes === -1 && file.charAt(i - 1) === ' ') { | ||
const before = file.slice(0, i - 1); | ||
const after = file.slice(i + 1); | ||
file = after; | ||
fname += ` (${before}`; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
if (fname) { | ||
const methodMatch = fname.match(methodRe); | ||
if (methodMatch) { | ||
fname = methodMatch[1]; | ||
method = methodMatch[2]; | ||
} | ||
} | ||
setFile(res, file, this._cwd); | ||
if (ctor) { | ||
res.constructor = true; | ||
} | ||
if (evalOrigin) { | ||
res.evalOrigin = evalOrigin; | ||
res.evalLine = evalLine; | ||
res.evalColumn = evalCol; | ||
res.evalFile = evalFile && evalFile.replace(/\\/g, '/'); | ||
} | ||
if (native) { | ||
res.native = true; | ||
} | ||
if (fname) { | ||
res.function = fname; | ||
} | ||
if (method && fname !== method) { | ||
res.method = method; | ||
} | ||
return res; | ||
} | ||
} | ||
return res; | ||
}; | ||
StackUtils.prototype._setFile = function (result, filename) { | ||
function setFile (result, filename, cwd) { | ||
if (filename) { | ||
filename = filename.replace(/\\/g, '/'); | ||
if ((filename.indexOf(this._cwd + '/') === 0)) { | ||
filename = filename.substr(this._cwd.length + 1); | ||
if (filename.startsWith(`${cwd}/`)) { | ||
filename = filename.slice(cwd.length + 1); | ||
} | ||
result.file = filename; | ||
} | ||
}; | ||
} | ||
var re = new RegExp( | ||
function ignoredPackagesRegExp(ignoredPackages) { | ||
if (ignoredPackages.length === 0) { | ||
return []; | ||
} | ||
const packages = ignoredPackages.map(mod => escapeStringRegexp(mod)); | ||
return new RegExp(`[\/\\\\]node_modules[\/\\\\](?:${packages.join('|')})[\/\\\\][^:]+:\\d+:\\d+`) | ||
} | ||
const re = new RegExp( | ||
'^' + | ||
@@ -238,95 +335,4 @@ // Sometimes we strip out the ' at' because it's noisy | ||
var methodRe = /^(.*?) \[as (.*?)\]$/; | ||
const methodRe = /^(.*?) \[as (.*?)\]$/; | ||
StackUtils.prototype.parseLine = function parseLine(line) { | ||
var match = line && line.match(re); | ||
if (!match) { | ||
return null; | ||
} | ||
var ctor = match[1] === 'new'; | ||
var fname = match[2]; | ||
var evalOrigin = match[3]; | ||
var evalFile = match[4]; | ||
var evalLine = Number(match[5]); | ||
var evalCol = Number(match[6]); | ||
var file = match[7]; | ||
var lnum = match[8]; | ||
var col = match[9]; | ||
var native = match[10] === 'native'; | ||
var closeParen = match[11] === ')'; | ||
var res = {}; | ||
if (lnum) { | ||
res.line = Number(lnum); | ||
} | ||
if (col) { | ||
res.column = Number(col); | ||
} | ||
if (closeParen && file) { | ||
// make sure parens are balanced | ||
// if we have a file like "asdf) [as foo] (xyz.js", then odds are | ||
// that the fname should be += " (asdf) [as foo]" and the file | ||
// should be just "xyz.js" | ||
// walk backwards from the end to find the last unbalanced ( | ||
var closes = 0; | ||
for (var i = file.length - 1; i > 0; i--) { | ||
if (file.charAt(i) === ')') { | ||
closes ++; | ||
} else if (file.charAt(i) === '(' && file.charAt(i - 1) === ' ') { | ||
closes --; | ||
if (closes === -1 && file.charAt(i - 1) === ' ') { | ||
var before = file.substr(0, i - 1); | ||
var after = file.substr(i + 1); | ||
file = after; | ||
fname += ' (' + before; | ||
break; | ||
} | ||
} | ||
} | ||
} | ||
if (fname) { | ||
var methodMatch = fname.match(methodRe); | ||
if (methodMatch) { | ||
fname = methodMatch[1]; | ||
var meth = methodMatch[2]; | ||
} | ||
} | ||
this._setFile(res, file); | ||
if (ctor) { | ||
res.constructor = true; | ||
} | ||
if (evalOrigin) { | ||
res.evalOrigin = evalOrigin; | ||
res.evalLine = evalLine; | ||
res.evalColumn = evalCol; | ||
res.evalFile = evalFile && evalFile.replace(/\\/g, '/'); | ||
} | ||
if (native) { | ||
res.native = true; | ||
} | ||
if (fname) { | ||
res.function = fname; | ||
} | ||
if (meth && fname !== meth) { | ||
res.method = meth; | ||
} | ||
return res; | ||
}; | ||
var bound = new StackUtils(); | ||
Object.keys(StackUtils.prototype).forEach(function (key) { | ||
StackUtils[key] = bound[key].bind(bound); | ||
}); | ||
module.exports = StackUtils; |
{ | ||
"name": "stack-utils", | ||
"version": "1.0.2", | ||
"version": "2.0.0", | ||
"description": "Captures and cleans stack traces", | ||
@@ -13,9 +13,9 @@ "license": "MIT", | ||
"engines": { | ||
"node": ">=0.10.0" | ||
"node": ">=10" | ||
}, | ||
"scripts": { | ||
"test": "tap test/*.js --100 -J", | ||
"test": "tap --no-esm --100", | ||
"preversion": "npm test", | ||
"postversion": "npm publish", | ||
"postpublish": "git push origin --all; git push origin --tags" | ||
"prepublish": "git push origin --follow-tags" | ||
}, | ||
@@ -25,15 +25,13 @@ "files": [ | ||
], | ||
"keywords": [ | ||
"" | ||
], | ||
"dependencies": {}, | ||
"dependencies": { | ||
"escape-string-regexp": "^2.0.0" | ||
}, | ||
"devDependencies": { | ||
"bluebird": "^3.1.1", | ||
"coveralls": "^2.11.6", | ||
"flatten": "0.0.1", | ||
"nested-error-stacks": "^2.0.0", | ||
"pify": "^2.3.0", | ||
"q": "^1.4.1", | ||
"tap": "^10.3.2" | ||
"bluebird": "^3.7.2", | ||
"coveralls": "^3.0.9", | ||
"nested-error-stacks": "^2.1.0", | ||
"pify": "^4.0.1", | ||
"q": "^1.5.1", | ||
"tap": "=14.10.2-unbundled" | ||
} | ||
} |
@@ -42,4 +42,14 @@ # stack-utils | ||
A set of regular expressions that match internal stack stack trace lines which should be culled from the stack trace. | ||
`StackUtils.nodeInternals()` returns a relatively set of sensible defaults for use on the node platform. | ||
The default is `StackUtils.nodeInternals()`, this can be disabled by setting `[]` or appended using | ||
`StackUtils.nodeInternals().concat(additionalRegExp)`. See also `ignoredPackages`. | ||
##### ignoredPackages | ||
Type: `array` of `string`s | ||
An array of npm modules to be culled from the stack trace. This list will mapped to regular | ||
expressions and merged with the `internals`. | ||
Default `''`. | ||
##### cwd | ||
@@ -63,3 +73,3 @@ | ||
### stackUtils.clean(stack) | ||
### stackUtils.clean(stack, indent = 0) | ||
@@ -69,2 +79,3 @@ Cleans up a stack trace by deleting any lines that match the `internals` passed to the constructor, and shortening file names relative to `cwd`. | ||
Returns a `string` with the cleaned up stack (always terminated with a `\n` newline character). | ||
Spaces at the start of each line are trimmed, indentation can be added by setting `indent` to the desired number of spaces. | ||
@@ -112,3 +123,3 @@ #### stack | ||
- `native`: `boolean` | ||
- `typename`: `string` | ||
- `type`: `string` | ||
- `function`: `string` | ||
@@ -115,0 +126,0 @@ - `method`: `string` |
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Major refactor
Supply chain riskPackage has recently undergone a major refactor. It may be unstable or indicate significant internal changes. Use caution when updating to versions that include significant changes.
Found 1 instance in 1 package
Debug access
Supply chain riskUses debug, reflection and dynamic code execution features.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
14356
6
144
1
271
2
1
+ Addedescape-string-regexp@^2.0.0
+ Addedescape-string-regexp@2.0.0(transitive)