332 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
		
		
			
		
	
	
			332 lines
		
	
	
		
			7.3 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
|  | '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; | ||
|  | } | ||
|  | 
 | ||
|  | module.exports.nodeInternals = nodeInternals; | ||
|  | 
 | ||
|  | function nodeInternals() { | ||
|  |   if (!module.exports.natives) { | ||
|  |     module.exports.natives = Object.keys(process.binding('natives')); | ||
|  |     module.exports.natives.push('bootstrap_node', 'node', | ||
|  |                                 'internal/bootstrap/node'); | ||
|  |   } | ||
|  | 
 | ||
|  |   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+\)?$/ | ||
|  |   ]); | ||
|  | } | ||
|  | 
 | ||
|  | StackUtils.prototype.clean = function (stack) { | ||
|  |   if (!Array.isArray(stack)) { | ||
|  |     stack = stack.split('\n'); | ||
|  |   } | ||
|  | 
 | ||
|  |   if (!(/^\s*at /.test(stack[0])) && | ||
|  |     (/^\s*at /.test(stack[1]))) { | ||
|  |     stack = stack.slice(1); | ||
|  |   } | ||
|  | 
 | ||
|  |   var outdent = false; | ||
|  |   var lastNonAtLine = null; | ||
|  |   var result = []; | ||
|  | 
 | ||
|  |   stack.forEach(function (st) { | ||
|  |     st = st.replace(/\\/g, '/'); | ||
|  |     var isInternal = this._internals.some(function (internal) { | ||
|  |       return internal.test(st); | ||
|  |     }); | ||
|  | 
 | ||
|  |     if (isInternal) { | ||
|  |       return null; | ||
|  |     } | ||
|  | 
 | ||
|  |     var isAtLine = /^\s*at /.test(st); | ||
|  | 
 | ||
|  |     if (outdent) { | ||
|  |       st = st.replace(/\s+$/, '').replace(/^(\s+)at /, '$1'); | ||
|  |     } else { | ||
|  |       st = st.trim(); | ||
|  |       if (isAtLine) { | ||
|  |         st = st.substring(3); | ||
|  |       } | ||
|  |     } | ||
|  | 
 | ||
|  |     st = st.replace(this._cwd + '/', ''); | ||
|  | 
 | ||
|  |     if (st) { | ||
|  |       if (isAtLine) { | ||
|  |         if (lastNonAtLine) { | ||
|  |           result.push(lastNonAtLine); | ||
|  |           lastNonAtLine = null; | ||
|  |         } | ||
|  |         result.push(st); | ||
|  |       } else { | ||
|  |         outdent = true; | ||
|  |         lastNonAtLine = st; | ||
|  |       } | ||
|  |     } | ||
|  |   }, this); | ||
|  | 
 | ||
|  |   stack = result.join('\n').trim(); | ||
|  | 
 | ||
|  |   if (stack) { | ||
|  |     return stack + '\n'; | ||
|  |   } | ||
|  |   return ''; | ||
|  | }; | ||
|  | 
 | ||
|  | StackUtils.prototype.captureString = function (limit, fn) { | ||
|  |   if (typeof limit === 'function') { | ||
|  |     fn = limit; | ||
|  |     limit = Infinity; | ||
|  |   } | ||
|  |   if (!fn) { | ||
|  |     fn = this.captureString; | ||
|  |   } | ||
|  | 
 | ||
|  |   var limitBefore = Error.stackTraceLimit; | ||
|  |   if (limit) { | ||
|  |     Error.stackTraceLimit = limit; | ||
|  |   } | ||
|  | 
 | ||
|  |   var obj = {}; | ||
|  | 
 | ||
|  |   Error.captureStackTrace(obj, fn); | ||
|  |   var stack = obj.stack; | ||
|  |   Error.stackTraceLimit = limitBefore; | ||
|  | 
 | ||
|  |   return this.clean(stack); | ||
|  | }; | ||
|  | 
 | ||
|  | StackUtils.prototype.capture = function (limit, fn) { | ||
|  |   if (typeof limit === 'function') { | ||
|  |     fn = limit; | ||
|  |     limit = Infinity; | ||
|  |   } | ||
|  |   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); | ||
|  |     } | ||
|  |     return site; | ||
|  |   }; | ||
|  | 
 | ||
|  |   if (limit) { | ||
|  |     Error.stackTraceLimit = limit; | ||
|  |   } | ||
|  | 
 | ||
|  |   var obj = {}; | ||
|  |   Error.captureStackTrace(obj, fn); | ||
|  |   var stack = obj.stack; | ||
|  |   Error.prepareStackTrace = prepBefore; | ||
|  |   Error.stackTraceLimit = limitBefore; | ||
|  | 
 | ||
|  |   return stack; | ||
|  | }; | ||
|  | 
 | ||
|  | StackUtils.prototype.at = function at(fn) { | ||
|  |   if (!fn) { | ||
|  |     fn = at; | ||
|  |   } | ||
|  | 
 | ||
|  |   var site = this.capture(1, fn)[0]; | ||
|  | 
 | ||
|  |   if (!site) { | ||
|  |     return {}; | ||
|  |   } | ||
|  | 
 | ||
|  |   var res = { | ||
|  |     line: site.getLineNumber(), | ||
|  |     column: site.getColumnNumber() | ||
|  |   }; | ||
|  | 
 | ||
|  |   this._setFile(res, site.getFileName()); | ||
|  | 
 | ||
|  |   if (site.isConstructor()) { | ||
|  |     res.constructor = true; | ||
|  |   } | ||
|  | 
 | ||
|  |   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; | ||
|  |   } | ||
|  | 
 | ||
|  |   var typename = null; | ||
|  |   try { | ||
|  |     typename = site.getTypeName(); | ||
|  |   } catch (er) {} | ||
|  | 
 | ||
|  |   if (typename && | ||
|  |     typename !== 'Object' && | ||
|  |     typename !== '[object Object]') { | ||
|  |     res.type = typename; | ||
|  |   } | ||
|  | 
 | ||
|  |   var fname = site.getFunctionName(); | ||
|  |   if (fname) { | ||
|  |     res.function = fname; | ||
|  |   } | ||
|  | 
 | ||
|  |   var meth = site.getMethodName(); | ||
|  |   if (meth && fname !== meth) { | ||
|  |     res.method = meth; | ||
|  |   } | ||
|  | 
 | ||
|  |   return res; | ||
|  | }; | ||
|  | 
 | ||
|  | StackUtils.prototype._setFile = function (result, filename) { | ||
|  |   if (filename) { | ||
|  |     filename = filename.replace(/\\/g, '/'); | ||
|  |     if ((filename.indexOf(this._cwd + '/') === 0)) { | ||
|  |       filename = filename.substr(this._cwd.length + 1); | ||
|  |     } | ||
|  |     result.file = filename; | ||
|  |   } | ||
|  | }; | ||
|  | 
 | ||
|  | var re = new RegExp( | ||
|  |   '^' + | ||
|  |     // Sometimes we strip out the '    at' because it's noisy
 | ||
|  |   '(?:\\s*at )?' + | ||
|  |     // $1 = ctor if 'new'
 | ||
|  |   '(?:(new) )?' + | ||
|  |     // $2 = function name (can be literally anything)
 | ||
|  |     // May contain method at the end as [as xyz]
 | ||
|  |   '(?:(.*?) \\()?' + | ||
|  |     // (eval at <anonymous> (file.js:1:1),
 | ||
|  |     // $3 = eval origin
 | ||
|  |     // $4:$5:$6 are eval file/line/col, but not normally reported
 | ||
|  |   '(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?' + | ||
|  |     // file:line:col
 | ||
|  |     // $7:$8:$9
 | ||
|  |     // $10 = 'native' if native
 | ||
|  |   '(?:(.+?):(\\d+):(\\d+)|(native))' + | ||
|  |     // maybe close the paren, then end
 | ||
|  |     // if $11 is ), then we only allow balanced parens in the filename
 | ||
|  |     // any imbalance is placed on the fname.  This is a heuristic, and
 | ||
|  |     // bound to be incorrect in some edge cases.  The bet is that
 | ||
|  |     // having weird characters in method names is more common than
 | ||
|  |     // having weird characters in filenames, which seems reasonable.
 | ||
|  |   '(\\)?)$' | ||
|  | ); | ||
|  | 
 | ||
|  | var 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); | ||
|  | }); |