Repository: watson/stackman Branch: master Commit: 8d0381086b25 Files: 19 Total size: 44.7 KB Directory structure: gitextract_2jhf8l14/ ├── .gitignore ├── .npmignore ├── .travis.yml ├── LICENSE ├── README.md ├── index.js ├── package.json └── test/ ├── fixtures/ │ ├── lib/ │ │ ├── error-broken.js │ │ ├── error-inline-broken.js │ │ ├── error-inline.js │ │ ├── error-map-missing.js │ │ ├── error-src-embedded.js │ │ ├── error-src-missing.js │ │ └── error.js │ └── src/ │ └── error.js ├── longjohn.js ├── non-strict.js ├── sourcemap.js └── strict.js ================================================ FILE CONTENTS ================================================ ================================================ FILE: .gitignore ================================================ # Logs logs *.log # Runtime data pids *.pid *.seed # Directory for instrumented libs generated by jscoverage/JSCover lib-cov # Coverage directory used by tools like istanbul coverage # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) .grunt # Compiled binary addons (http://nodejs.org/api/addons.html) build/Release # Dependency directory # Deployed apps should consider commenting this line out: # see https://npmjs.org/doc/faq.html#Should-I-check-my-node_modules-folder-into-git node_modules ================================================ FILE: .npmignore ================================================ .travis.yml test ================================================ FILE: .travis.yml ================================================ language: node_js node_js: - '13' - '12' - '11' - '10' - '9' - '8' - '7' - '6' ================================================ FILE: LICENSE ================================================ The MIT License (MIT) Copyright (c) 2015-2020 Thomas Watson Steen Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. ================================================ FILE: README.md ================================================ # Stackman Give Stackman an error and he will give an array of stack frames with extremely detailed information for each frame in the stack trace. With Stackman you get access to the actual source code and surrounding lines for where the error occurred, you get to know if it happened inside a 3rd party module, in Node.js or in your own code. For a full list of information, check out the API below. [![npm](https://img.shields.io/npm/v/stackman.svg)](https://www.npmjs.com/package/stackman) [![Build status](https://travis-ci.org/watson/stackman.svg?branch=master)](https://travis-ci.org/watson/stackman) [![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg?style=flat)](https://github.com/feross/standard) [![sponsor](https://img.shields.io/badge/sponsored%20by-Elastic-3360A3.svg)](https://www.elastic.co) ## Install ``` npm install stackman ``` ## Basic usage ```javascript var stackman = require('stackman')() var err = new Error('Oops!') stackman.callsites(err, function (err, callsites) { if (err) throw err callsites.forEach(function (callsite) { console.log('Error occured in at %s line %d', callsite.getFileName(), callsite.getLineNumber()) }) }) ``` ## Gotchas ### `error.stack` This module works because V8 (the JavaScript engine behind Node.js) allows us to hook into the stack trace generator function before that stack trace is generated. It's triggered by accessing the `.stack` property on the Error object, so please don't do that before parsing the error to stackman, else this will not work! If you want to output the regular stack trace, just do so after parsing the callsites: ```javascript // first call stackman.callsites with the error stackman.callsites(err, function () {...}) // then you can print out the stack trace console.log(err.stack) ``` ## Stackman API ### `var stackman = Stackman([options])` This module exposes a single function which you must call to get a `stackman` object. The function takes an optional options object as its only argument. These are the available options: - `fileCacheMax` - When source files are read from disk, they are kept in memory in an LRU cache to speed up processing of future errors. You can change the max number of files kept in the LRU cache using this property (default: 500) - `sourceMapCacheMax` - When source maps are read from disk, the processed source maps are kept in memory in an LRU cache to speed up processing of future errors. You can change the max number of source maps kept in the LRU cache using this property (default: 100) ### `stackman.callsites(err[, options], callback)` Given an error object, this function will call the `callback` with an optional error as the first argument and an array of [CallSite](#callsite-api) objects as the 2nd (a call site is a frame in the stack trace). Note that any error related to loading or parsing source maps will be suppressed. If a source map related error occurs, Stackman behaves as if the `sourcemap` option is `false`. Options: - `sourcemap` - A boolean specifying if Stackman should look for and process source maps (default: `true`) ### `var properties = stackman.properties(err)` Given an error object, this function will return an object containing all the custom properties from the original error object (beside date objects, properties of type `object` and `function` are not included in this object). ### `stackman.sourceContexts(callsites[, options], callback)` Convenience function to get the source context for all call sites in the `callsites` argument in one go (instead of iterating over the call sites and calling [`callsite.sourceContext()`](#callsitesourcecontextoptions-callback) for each of them). Calls the `callback` with an optional error object as the first argument and an array of [source context objects](#source-context) as the 2nd. Each element in the context array matches a call site in the `callsites` array. Options: - `lines` - Total number of lines of soruce context to be loaded with the call site line in the center (default: `5`) - `inAppLines` - Total number of lines of soruce context to be loaded with the call site line in the center if `callsite.isApp()` is `true`. Overwrites `lines` (default: `5`) - `libraryLines` - Number of lines of soruce context to be loaded with the call site line in the center if `callsite.isApp()` is `false`. Overwrites `lines` (default: `5`) All node core call sites and call sites where no lines were collected due to the above options being `0`, will have the context value `null`. ## CallSite API A CallSite object is an object provided by the [V8 stack trace API](https://github.com/v8/v8/wiki/Stack-Trace-API) representing a frame in the stack trace. Stackman will decorate each CallSite object with custom functions and behavior. ### `callsite.sourcemap` If source map support is enabled and a source map have been found for the CallSite, this property will be a reference to a [`SourceMapConsumer`](https://github.com/mozilla/source-map#sourcemapconsumer) object representing the given CallSite. If set, all functions on the CallSite object will be source map aware. I.e. their return values will be related to the original source code and not the transpiled source code. ### `var val = callsite.getThis()` _Inherited from V8_ Returns the value of `this`. To maintain restrictions imposed on strict mode functions, frames that have a strict mode function and all frames below (its caller etc.) are not allow to access their receiver and function objects. For those frames, `getThis()` will return `undefined`. ### `var str = callsite.getTypeName()` _Inherited from V8_ Returns the type of `this` as a string. This is the name of the function stored in the constructor field of `this`, if available, otherwise the object's `[[Class]]` internal property. ### `var str = callsite.getTypeNameSafely()` A safer version of [`callsite.getTypeName()`](#var-str--callsitegettypename) that safely handles an exception that sometimes is thrown when using `"use strict"` in which case `null` is returned. ### `var fn = callsite.getFunction()` _Inherited from V8_ Returns the current function. To maintain restrictions imposed on strict mode functions, frames that have a strict mode function and all frames below (its caller etc.) are not allow to access their receiver and function objects. For those frames, `getFunction()` will return `undefined`. ### `var str = callsite.getFunctionName()` _Inherited from V8_ Returns the name of the current function, typically its name property. If a name property is not available an attempt will be made to try to infer a name from the function's context. ### `var str = callsite.getFunctionNameSanitized()` Guaranteed to always return the most meaningful function name. If none can be determined, the string `` will be returned. ### `var str = callsite.getMethodName()` _Inherited from V8_ Returns the name of the property of this or one of its prototypes that holds the current function. ### `var str = callsite.getFileName()` _Inherited from V8 if `callsite.sourcemap` is `undefined`_ If this function was defined in a script returns the name of the script. ### `var str = callsite.getRelativeFileName()` Returns a filename realtive to `process.cwd()`. ### `var num = callsite.getLineNumber()` _Inherited from V8 if `callsite.sourcemap` is `undefined`_ If this function was defined in a script returns the current line number. ### `var num = callsite.getColumnNumber()` _Inherited from V8 if `callsite.sourcemap` is `undefined`_ If this function was defined in a script returns the current column number. ### `var str = callsite.getEvalOrigin()` _Inherited from V8_ If this function was created using a call to eval returns a CallSite object representing the location where eval was called. Note that since Node.js v12.11.0, this function returns `undefined` unless `eval` was used. ### `var str = callsite.getModuleName()` Returns the name of the module if `isModule()` is `true`. Otherwise returns `null`. ### `var bool = callsite.isToplevel()` _Inherited from V8_ Is this a toplevel invocation, that is, is this the global object? ### `var bool = callsite.isEval()` _Inherited from V8_ Does this call take place in code defined by a call to eval? ### `var bool = callsite.isNative()` _Inherited from V8_ Is this call in native V8 code? ### `var bool = callsite.isConstructor()` _Inherited from V8_ Is this a constructor call? ### `var bool = callsite.isApp()` Is this inside the app? (i.e. not native, not node code and not a module inside the `node_modules` directory) ### `var bool = callsite.isModule()` Is this inside the `node_modules` directory? ### `var bool = callsite.isNode()` Is this inside node core? ### `callsite.sourceContext([lines, ]callback)` Get the source code surrounding the call site line. If the `callsite` is a node core call site, the `callback` will be called with an error. Arguments: - `lines` - Total number of lines of soruce context to be loaded with the call site line in the center (default: `5`) - `callback` - called when the source context have been loaded with an optional error object as the first argument and a [source context object](#source-context) as the 2nd ## Source Context The source context objects provided by [`callsite.sourceContext`](#callsitesourcecontextoptions-callback) contains the following properties: - `pre` - The lines before the main callsite line - `line` - The main callsite line - `post` - The lines after the main callsite line ## Troubleshooting To enable debug mode, set the environment variable `DEBUG=stackman`. ## Acknowledgements This project was kindly sponsored by [Elastic](https://www.elastic.co). ## License [MIT](LICENSE) ================================================ FILE: index.js ================================================ 'use strict' var fs = require('fs') var path = require('path') var asyncCache = require('async-cache') var afterAll = require('after-all-results') var errorCallsites = require('error-callsites') var loadSourceMap = require('load-source-map') var debug = require('debug')('stackman') var LINES_OF_CONTEXT = 5 var ESCAPED_REGEX_PATH_SEP = path.sep === '/' ? '/' : '\\\\' var MODULE_FOLDER_REGEX = new RegExp('.*node_modules' + ESCAPED_REGEX_PATH_SEP + '([^' + ESCAPED_REGEX_PATH_SEP + ']*)') module.exports = function stackman (opts) { if (!opts) opts = {} var fileCache = asyncCache({ max: opts.fileCacheMax || 500, load: function (file, cb) { debug('reading %s', file) fs.readFile(file, { encoding: 'utf8' }, function (err, data) { if (err) return cb(err) cb(null, data.split(/\r?\n/)) }) } }) var sourceMapCache = asyncCache({ max: opts.sourceMapCacheMax || 100, load: function (file, cb) { debug('loading source map for %s', file) loadSourceMap(file, cb) } }) return { callsites: callsites, properties: properties, sourceContexts: sourceContexts } function callsites (err, opts, cb) { if (typeof opts === 'function') return callsites(err, null, opts) var _callsites = errorCallsites(err) if (!validStack(_callsites)) { var _err = new Error('Could not process callsites') process.nextTick(function () { cb(_err) }) } else if (!opts || opts.sourcemap !== false) { sourcemapify(_callsites, function (err) { if (err) { debug('error processing source map: %s', err.message) } _callsites.forEach(extendCallsite) cb(null, _callsites) }) } else { _callsites.forEach(extendCallsite) process.nextTick(function () { cb(null, _callsites) }) } } function properties (err) { var properties = {} Object.keys(err).forEach(function (key) { if (key === 'stack') return // 'stack' seems to be enumerable in Node 0.11 var val = err[key] if (val === null) return // null is typeof object and well break the switch below switch (typeof val) { case 'function': return case 'object': // ignore all objects except Dates if (typeof val.toISOString !== 'function') return val = val.toISOString() } properties[key] = val }) return properties } function sourceContexts (callsites, opts, cb) { if (typeof opts === 'function') return sourceContexts(callsites, null, opts) if (!opts) opts = {} opts.inAppLines = opts.inAppLines >= 0 ? opts.inAppLines : (opts.lines || LINES_OF_CONTEXT) opts.libraryLines = opts.libraryLines >= 0 ? opts.libraryLines : (opts.lines || LINES_OF_CONTEXT) var next = afterAll(cb) callsites.forEach(function (callsite) { var lines = callsite.isApp() ? opts.inAppLines : opts.libraryLines if (lines > 0 && !callsite.isNode()) { callsite.sourceContext(lines, next()) } else { next()(null, null) } }) } function validStack (callsites) { return Array.isArray(callsites) && typeof callsites[0] === 'object' && typeof callsites[0].getFileName === 'function' } function getRelativeFileName () { var filename = this.getFileName() if (!filename) return var root = process.cwd() if (root[root.length - 1] !== path.sep) root += path.sep return !~filename.indexOf(root) ? filename : filename.substr(root.length) } function getTypeNameSafely () { try { return this.getTypeName() } catch (e) { // This seems to happen sometimes when using 'use strict', // stemming from `getTypeName`. // [TypeError: Cannot read property 'constructor' of undefined] return null } } function getFunctionNameSanitized () { var fnName = this.getFunctionName() if (fnName) return fnName var typeName = this.getTypeNameSafely() if (typeName) return typeName + '.' + (this.getMethodName() || '') return '' } function getModuleName () { var filename = this.getFileName() || '' var match = filename.match(MODULE_FOLDER_REGEX) return match ? match[1] : null } function isApp () { return !this.isNode() && !~(this.getFileName() || '').indexOf('node_modules' + path.sep) } function isModule () { return !!~(this.getFileName() || '').indexOf('node_modules' + path.sep) } function isNode () { if (this.isNative()) return true var filename = this.getFileName() || '' return (!path.isAbsolute(filename) && filename[0] !== '.') } function sourceContext (linesOfContext, cb) { var _err if (typeof linesOfContext === 'function') { cb = linesOfContext linesOfContext = LINES_OF_CONTEXT } if (linesOfContext <= 0) { _err = new Error('Cannot collect less than one line of source context') process.nextTick(function () { cb(_err) }) return } if (this.isNode()) { _err = new Error('Can\'t get source context of a Node core callsite') process.nextTick(function () { cb(_err) }) return } var callsite = this var filename = this.getFileName() || '' var source = this.sourcemap ? this.sourcemap.sourceContentFor(filename, true) : null if (source) { process.nextTick(function () { cb(null, parseSource(source, callsite, linesOfContext)) }) } else { fileCache.get(filename, function (err, lines) { if (err) { debug('error reading %s: %s', filename, err.message) cb(err) } else { cb(null, parseSource(lines, callsite, linesOfContext)) } }) } } function parseSource (lines, callsite, linesOfContext) { var index = callsite.getLineNumber() - 1 var preLinesOfContext = Math.ceil((linesOfContext - 1) / 2) var postLinesOfContext = Math.floor((linesOfContext - 1) / 2) return { pre: lines.slice(Math.max(0, index - preLinesOfContext), index), line: lines[index], post: lines.slice(index + 1, index + 1 + postLinesOfContext) } } function sourcemapify (callsites, cb) { var next = afterAll(function (err, consumers) { if (err) return cb(err) consumers.forEach(function (consumer, index) { if (!consumer) return Object.defineProperty(callsites[index], 'sourcemap', { writable: true, value: consumer }) }) cb() }) callsites.forEach(function (callsite) { getSourceMapConsumer(callsite, next()) }) } function getSourceMapConsumer (callsite, cb) { if (isNode.call(callsite)) return process.nextTick(cb) var filename = callsite.getFileName() sourceMapCache.get(filename, cb) } function extendCallsite (callsite) { var getLineNumber = callsite.getLineNumber var getColumnNumber = callsite.getColumnNumber var getFileName = callsite.getFileName var position = null var properties = { getRelativeFileName: { writable: true, value: getRelativeFileName }, getTypeNameSafely: { writable: true, value: getTypeNameSafely }, getFunctionNameSanitized: { writable: true, value: getFunctionNameSanitized }, getModuleName: { writable: true, value: getModuleName }, isApp: { writable: true, value: isApp }, isModule: { writable: true, value: isModule }, isNode: { writable: true, value: isNode }, sourceContext: { writable: true, value: sourceContext } } if (callsite.sourcemap) { properties.getFileName = { writable: true, value: function () { var filename = getFileName.call(callsite) var sourceFile = getPosition().source if (!sourceFile) return filename var sourceDir = path.dirname(filename) return path.resolve(path.join(sourceDir, sourceFile)) } } properties.getLineNumber = { writable: true, value: function () { return getPosition().line || getLineNumber.call(callsite) } } properties.getColumnNumber = { writable: true, value: function () { return getPosition().column || getColumnNumber.call(callsite) } } } Object.defineProperties(callsite, properties) function getPosition () { if (!position) { try { position = callsite.sourcemap.originalPositionFor({ line: getLineNumber.call(callsite), column: getColumnNumber.call(callsite) }) } catch (e) { debug('error fetching source map position: %s', e.message) return {} } } return position } } } ================================================ FILE: package.json ================================================ { "name": "stackman", "version": "4.0.1", "description": "Enhance an error stacktrace with code excerpts and other goodies", "main": "index.js", "dependencies": { "after-all-results": "^2.0.0", "async-cache": "^1.1.0", "debug": "^4.1.1", "error-callsites": "^2.0.3", "load-source-map": "^1.0.0" }, "devDependencies": { "longjohn": "^0.2.12", "semver": "^6.3.0", "standard": "^14.3.3", "tape": "^4.13.2" }, "scripts": { "test": "standard && node test/strict.js && node test/non-strict.js && node test/longjohn.js && node test/sourcemap.js" }, "repository": { "type": "git", "url": "git://github.com/watson/stackman.git" }, "keywords": [ "v8", "stack", "stacktrace", "stackframe", "callsite", "callsites", "frame", "source", "debug", "log", "error", "trace" ], "author": "Thomas Watson Steen ", "license": "MIT", "bugs": { "url": "https://github.com/watson/stackman/issues" }, "homepage": "https://github.com/watson/stackman", "coordinates": [ 56.043843, 12.609477 ], "standard": { "ignore": [ "/test/fixtures/lib/" ] } } ================================================ FILE: test/fixtures/lib/error-broken.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=error-broken.js.map ================================================ FILE: test/fixtures/lib/error-inline-broken.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9lcnJvci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBO0FBQ0EsSUFBTSxnQkFBZ0IsU0FBaEIsYUFBZ0I7QUFBQSxNQUFDLEdBQUQsdUVBQU8sS0FBUDtBQUFBLFNBQWlCLElBQUksS0FBSixDQUFVLEdBQVYsQ0FBakI7QUFBQSxDQUF0Qjs7QUFFQSxPQUFPLE9BQVAsR0FBaUIsYUFBakIiLCJmaWxlIjoiZXJyb3ItaW5saW5lLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gSnVzdCBhIGxpdHRsZSBwcmVmaXhpbmcgbGluZVxuY29uc3QgZ2VuZXJhdGVFcnJvciA9IChtc2cgPSAnZm9vJykgPT4gbmV3IEVycm9yKG1zZylcblxubW9kdWxlLmV4cG9ydHMgPSBnZW5lcmF0ZUVycm9yXG4iXX0= ================================================ FILE: test/fixtures/lib/error-inline.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIi4uL3NyYy9lcnJvci5qcyJdLCJuYW1lcyI6W10sIm1hcHBpbmdzIjoiOztBQUFBO0FBQ0EsSUFBTSxnQkFBZ0IsU0FBaEIsYUFBZ0I7QUFBQSxNQUFDLEdBQUQsdUVBQU8sS0FBUDtBQUFBLFNBQWlCLElBQUksS0FBSixDQUFVLEdBQVYsQ0FBakI7QUFBQSxDQUF0Qjs7QUFFQSxPQUFPLE9BQVAsR0FBaUIsYUFBakIiLCJmaWxlIjoiZXJyb3ItaW5saW5lLmpzIiwic291cmNlc0NvbnRlbnQiOlsiLy8gSnVzdCBhIGxpdHRsZSBwcmVmaXhpbmcgbGluZVxuY29uc3QgZ2VuZXJhdGVFcnJvciA9IChtc2cgPSAnZm9vJykgPT4gbmV3IEVycm9yKG1zZylcblxubW9kdWxlLmV4cG9ydHMgPSBnZW5lcmF0ZUVycm9yXG4iXX0= ================================================ FILE: test/fixtures/lib/error-map-missing.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=invalid.js.map ================================================ FILE: test/fixtures/lib/error-src-embedded.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=error-src-embedded.js.map ================================================ FILE: test/fixtures/lib/error-src-missing.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=error-src-missing.js.map ================================================ FILE: test/fixtures/lib/error.js ================================================ 'use strict'; // Just a little prefixing line var generateError = function generateError() { var msg = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : 'foo'; return new Error(msg); }; module.exports = generateError; //# sourceMappingURL=error.js.map ================================================ FILE: test/fixtures/src/error.js ================================================ // Just a little prefixing line const generateError = (msg = 'foo') => new Error(msg) module.exports = generateError ================================================ FILE: test/longjohn.js ================================================ 'use strict' require('longjohn') var test = require('tape') var stackman = require('../')() test('longjohn, regular error', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.ok(callsites.length > 0, 'should have stack frames') t.end() }) }) test('longjohn, thrown error', function (t) { try { throw new Error('foo') } catch (err) { stackman.callsites(err, function (err, callsites) { t.error(err) t.ok(callsites.length > 0, 'should have stack frames') t.end() }) } }) ================================================ FILE: test/non-strict.js ================================================ var test = require('tape') var stackman = require('../')() test('callsite.getThis()', function (t) { var err = new Error('foo') var self = this stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getThis(), self) t.end() }) }) test('callsite.getFunction()', function fn (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFunction(), fn) t.end() }) }) ================================================ FILE: test/sourcemap.js ================================================ 'use strict' var path = require('path') var test = require('tape') var stackman = require('../')() test('source maps disabled', function (t) { var err = require('./fixtures/lib/error')() stackman.callsites(err, { sourcemap: false }, assertSourceMapNotLoaded.bind(null, t, 'error.js')) }) test('source map inlined', function (t) { var err = require('./fixtures/lib/error-inline')() stackman.callsites(err, assertSourceFound.bind(null, t)) }) test('source map linked', function (t) { t.test('source mapped source code embedded', function (t) { var err = require('./fixtures/lib/error-src-embedded')() stackman.callsites(err, assertSourceFound.bind(null, t)) }) t.test('source mapped source code on disk', function (t) { var err = require('./fixtures/lib/error')() stackman.callsites(err, assertSourceFound.bind(null, t)) }) t.test('source mapped source code not found', function (t) { var err = require('./fixtures/lib/error-src-missing')() stackman.callsites(err, assertSourceNotFound.bind(null, t)) }) }) test('fails', function (t) { t.test('inlined source map broken', function (t) { var err = require('./fixtures/lib/error-inline-broken')() stackman.callsites(err, assertSourceMapNotLoaded.bind(null, t, 'error-inline-broken.js')) }) t.test('linked source map not found', function (t) { var err = require('./fixtures/lib/error-map-missing')() stackman.callsites(err, assertSourceMapNotLoaded.bind(null, t, 'error-map-missing.js')) }) t.test('linked source map broken', function (t) { var err = require('./fixtures/lib/error-broken')() stackman.callsites(err, assertSourceMapNotLoaded.bind(null, t, 'error-broken.js')) }) }) function assertSourceMapNotLoaded (t, filename, err, callsites) { t.error(err) var callsite = callsites[0] t.equal(callsite.getFileName(), path.join(__dirname, 'fixtures', 'lib', filename)) t.equal(callsite.getRelativeFileName(), path.join('test', 'fixtures', 'lib', filename)) t.equal(callsite.getLineNumber(), 6) t.equal(callsite.getColumnNumber(), 10) t.equal(callsite.getFunctionName(), 'generateError') t.equal(callsite.getFunctionNameSanitized(), 'generateError') t.equal(callsite.isApp(), __dirname.indexOf('node_modules') === -1) callsite.sourceContext(function (err, context) { t.error(err) t.equal(context.line, ' return new Error(msg);') t.end() }) } function assertSourceFound (t, err, callsites) { t.error(err) var callsite = callsites[0] t.equal(callsite.getFileName(), path.join(__dirname, 'fixtures', 'src', 'error.js')) t.equal(callsite.getRelativeFileName(), path.join('test', 'fixtures', 'src', 'error.js')) t.equal(callsite.getLineNumber(), 2) t.equal(callsite.getColumnNumber(), 39) t.equal(callsite.getFunctionName(), 'generateError') t.equal(callsite.getFunctionNameSanitized(), 'generateError') t.equal(callsite.isApp(), __dirname.indexOf('node_modules') === -1) callsite.sourceContext(function (err, context) { t.error(err) t.deepEqual(context.pre, ['// Just a little prefixing line']) t.equal(context.line, 'const generateError = (msg = \'foo\') => new Error(msg)') t.deepEqual(context.post, ['', 'module.exports = generateError']) t.end() }) } function assertSourceNotFound (t, err, callsites) { t.error(err) var callsite = callsites[0] t.equal(callsite.getFileName(), path.join(__dirname, 'fixtures', 'src', 'not', 'found.js')) t.equal(callsite.getRelativeFileName(), path.join('test', 'fixtures', 'src', 'not', 'found.js')) t.equal(callsite.getLineNumber(), 2) t.equal(callsite.getColumnNumber(), 39) t.equal(callsite.getFunctionName(), 'generateError') t.equal(callsite.getFunctionNameSanitized(), 'generateError') t.equal(callsite.isApp(), __dirname.indexOf('node_modules') === -1) callsite.sourceContext(function (err, context) { t.equal(err.code, 'ENOENT') t.equal(context, undefined) t.end() }) } ================================================ FILE: test/strict.js ================================================ 'use strict' var fs = require('fs') var path = require('path') var semver = require('semver') var test = require('tape') var stackman = require('../')() test('stackman.callsites()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.ok(Array.isArray(callsites)) t.ok(callsites.length > 0, 'should have at least one element') callsites.forEach(function (callsite, index) { t.equal(typeof callsite.getThis, 'function', 'getThis should be a function') t.equal(typeof callsite.getTypeName, 'function', 'getTypeName should be a function') t.equal(typeof callsite.getTypeNameSafely, 'function', 'getTypeNameSafely should be a function') t.equal(typeof callsite.getFunction, 'function', 'getFunction should be a function') t.equal(typeof callsite.getFunctionName, 'function', 'getFunctionName should be a function') t.equal(typeof callsite.getFunctionNameSanitized, 'function', 'getFunctionNameSanitized should be a function') t.equal(typeof callsite.getMethodName, 'function', 'getMethodName should be a function') t.equal(typeof callsite.getFileName, 'function', 'getFileName should be a function') t.equal(typeof callsite.getRelativeFileName, 'function', 'getRelativeFileName should be a function') t.equal(typeof callsite.getLineNumber, 'function', 'getLineNumber should be a function') t.equal(typeof callsite.getColumnNumber, 'function', 'getColumnNumber should be a function') t.equal(typeof callsite.getEvalOrigin, 'function', 'getEvalOrigin should be a function') t.equal(typeof callsite.getModuleName, 'function', 'getModuleName should be a function') t.equal(typeof callsite.isToplevel, 'function', 'isToplevel should be a function') t.equal(typeof callsite.isEval, 'function', 'isEval should be a function') t.equal(typeof callsite.isNative, 'function', 'isNative should be a function') t.equal(typeof callsite.isConstructor, 'function', 'isConstructor should be a function') t.equal(typeof callsite.isApp, 'function', 'isApp should be a function') t.equal(typeof callsite.isModule, 'function', 'isModule should be a function') t.equal(typeof callsite.isNode, 'function', 'isNode should be a function') t.equal(typeof callsite.sourceContext, 'function', 'sourceContext should be a function') }) t.end() }) }) test('callsite.getThis()', function (t) { var err = new Error('foo') var self = this stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] if (semver.gte(process.version, '0.12.0')) { t.equal(callsite.getThis(), undefined) } else { t.equal(callsite.getThis(), self) } t.end() }) }) test('callsite.getTypeName()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getTypeName(), 'Test') t.end() }) }) test('callsite.getTypeNameSafely()', function (t) { // TODO: It would be nice if we could get the non-safe version to // throw in a test var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getTypeNameSafely(), 'Test') t.end() }) }) test('callsite.getFunction()', function fn (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] if (semver.gte(process.version, '0.12.0')) { t.equal(callsite.getFunction(), undefined) } else { t.equal(callsite.getFunction(), fn) } t.end() }) }) test('callsite.getFunctionName() - anonymous', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFunctionName(), null) t.end() }) }) test('callsite.getFunctionName() - named', function named (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFunctionName(), 'named') t.end() }) }) test('callsite.getFunctionNameSanitized() - anonymous', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFunctionNameSanitized(), 'Test.') t.end() }) }) test('callsite.getFunctionNameSanitized() - named', function named (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFunctionNameSanitized(), 'named') t.end() }) }) test('callsite.getMethodName()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getMethodName(), null) t.end() }) }) test('callsite.getFileName()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getFileName(), __filename) t.end() }) }) test('callsite.getRelativeFileName()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getRelativeFileName(), 'test/strict.js') t.end() }) }) test('callsite.getLineNumber()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.ok(callsites[0].getLineNumber() > 1) t.end() }) }) test('callsite.getColumnNumber()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getColumnNumber(), 13) t.end() }) }) test('callsite.getEvalOrigin()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].getEvalOrigin(), semver.gte(process.version, '12.11.0') ? undefined : __filename) t.end() }) }) test('callsite.getEvalOrigin()', function (t) { var err = eval('new Error(\'foo\')') // eslint-disable-line no-eval stackman.callsites(err, function (err, callsites) { t.error(err) var actual = callsites[0].getEvalOrigin() var expected = new RegExp(`^eval at \\(${__filename}:\\d+:\\d+\\)$`) t.ok(expected.test(actual), 'should match regex', { actual, expected }) t.end() }) }) test('callsite.getModuleName()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] if (__filename.indexOf(path.sep + 'node_modules' + path.sep) === -1) { t.equal(callsite.getModuleName(), null) } else { t.equal(callsite.getModuleName(), 'stackman') } t.end() }) }) test('callsite.isToplevel()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].isToplevel(), false) t.end() }) }) test('callsite.isEval()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].isEval(), false) t.end() }) }) test('callsite.isNative()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].isNative(), false) t.end() }) }) test('callsite.isConstructor()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].isConstructor(), false) t.end() }) }) test('callsite.isApp()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] if (__filename.indexOf(path.sep + 'node_modules' + path.sep) === -1) { t.equal(callsite.isApp(), true) } else { t.equal(callsite.isApp(), false) } t.end() }) }) test('callsite.isModule()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] if (__filename.indexOf(path.sep + 'node_modules' + path.sep) === -1) { t.equal(callsite.isModule(), false) } else { t.equal(callsite.isModule(), true) } t.end() }) }) test('callsite.isNode()', function (t) { var err = new Error('foo') stackman.callsites(err, function (err, callsites) { t.error(err) t.equal(callsites[0].isNode(), false) t.end() }) }) test('callsite.sourceContext()', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(function (err, context) { t.error(err) t.equal(typeof context, 'object') t.equal(typeof context.line, 'string') t.ok(Array.isArray(context.pre), 'should be an array') t.ok(Array.isArray(context.post), 'should be an array') t.equal(context.pre.length, 2) t.equal(context.post.length, 2) t.equal(context.line.trim(), 'var err = new Error()') t.end() }) }) }) test('callsite.sourceContext() - node core', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) var callsite = callsites[0] Object.defineProperty(callsite, 'isNode', { writable: true, value: function () { return true } }) callsites[0].sourceContext(function (err, context) { t.equal(err.message, 'Can\'t get source context of a Node core callsite') t.end() }) }) }) test('callsite.sourceContext(0)', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(0, function (err, context) { t.equal(err.message, 'Cannot collect less than one line of source context') t.notOk(context) t.end() }) }) }) test('callsite.sourceContext(1)', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(1, function (err, context) { t.error(err) t.equal(context.pre.length, 0) t.equal(context.line.trim(), 'var err = new Error()') t.equal(context.post.length, 0) t.end() }) }) }) test('callsite.sourceContext(2)', function (t) { // line before var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(2, function (err, context) { t.error(err) t.equal(context.pre.length, 1) t.equal(context.pre[0].trim(), '// line before') t.equal(context.line.trim(), 'var err = new Error()') t.equal(context.post.length, 0) t.end() }) }) }) test('callsite.sourceContext(3)', function (t) { // line before var err = new Error() // line after stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(3, function (err, context) { t.error(err) t.equal(context.pre.length, 1) t.equal(context.pre[0].trim(), '// line before') t.equal(context.line.trim(), 'var err = new Error()') t.equal(context.post.length, 1) t.equal(context.post[0].trim(), '// line after') t.end() }) }) }) test('callsite.sourceContext(4)', function (t) { // line before 2 // line before 1 var err = new Error() // line after stackman.callsites(err, function (err, callsites) { t.error(err) callsites[0].sourceContext(4, function (err, context) { t.error(err) t.equal(context.pre.length, 2) t.equal(context.pre[0].trim(), '// line before 2') t.equal(context.pre[1].trim(), '// line before 1') t.equal(context.line.trim(), 'var err = new Error()') t.equal(context.post.length, 1) t.equal(context.post[0].trim(), '// line after') t.end() }) }) }) test('stackman.properties()', function (t) { fs.readFile('./no_such_file', function (err) { err.foo = 'bar' var props = stackman.properties(err) t.equal(props.errno, err.errno) t.equal(props.code, 'ENOENT') t.equal(props.path, './no_such_file') t.equal(props.foo, 'bar') t.end() }) }) test('stackman.sourceContexts(callsites)', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) stackman.sourceContexts(callsites, function (err, contexts) { t.error(err) contexts.forEach(function (context, index) { var callsite = callsites[index] if (callsite.isNode()) { t.equal(context, null) } else { t.equal(typeof context, 'object') t.equal(typeof context.line, 'string') t.ok(Array.isArray(context.pre), 'should be an array') t.ok(Array.isArray(context.post), 'should be an array') t.equal(context.pre.length, 2) t.equal(context.post.length, 2) } }) t.end() }) }) }) test('stackman.sourceContexts(callsites, {lines: 7})', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) stackman.sourceContexts(callsites, { lines: 7 }, function (err, contexts) { t.error(err) contexts.forEach(function (context, index) { var callsite = callsites[index] if (callsite.isNode()) { t.equal(context, null) } else { t.equal(typeof context, 'object') t.equal(typeof context.line, 'string') t.ok(Array.isArray(context.pre), 'should be an array') t.ok(Array.isArray(context.post), 'should be an array') t.equal(context.pre.length, 3) t.equal(context.post.length, 3) } }) t.end() }) }) }) test('stackman.sourceContexts(callsites, {inAppLines: 7, libraryLines: 3})', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) stackman.sourceContexts(callsites, { inAppLines: 7, libraryLines: 3 }, function (err, contexts) { t.error(err) contexts.forEach(function (context, index) { var callsite = callsites[index] if (callsite.isNode()) { t.equal(context, null) } else { t.equal(typeof context, 'object') t.equal(typeof context.line, 'string') t.ok(Array.isArray(context.pre), 'should be an array') t.ok(Array.isArray(context.post), 'should be an array') if (callsite.isApp()) { t.equal(context.pre.length, 3) t.equal(context.post.length, 3) } else { t.equal(context.pre.length, 1) t.equal(context.post.length, 1) } } }) t.end() }) }) }) test('stackman.sourceContexts(callsites, {libraryLines: 0})', function (t) { var err = new Error() stackman.callsites(err, function (err, callsites) { t.error(err) stackman.sourceContexts(callsites, { libraryLines: 0 }, function (err, contexts) { t.error(err) contexts.forEach(function (context, index) { var callsite = callsites[index] if (callsite.isApp()) { t.equal(typeof context, 'object') t.equal(typeof context.line, 'string') t.ok(Array.isArray(context.pre), 'should be an array') t.ok(Array.isArray(context.post), 'should be an array') t.equal(context.pre.length, 2) t.equal(context.post.length, 2) } else { t.equal(context, null) } }) t.end() }) }) })