├── .editorconfig ├── .gitattributes ├── .github └── workflows │ ├── ci.yml │ ├── commit-if-modified.sh │ ├── copyright-year.sh │ ├── isaacs-makework.yml │ └── package-json-repo.js ├── .gitignore ├── LICENSE.md ├── index.js ├── package-lock.json ├── package.json ├── readme.md └── test ├── _utils.js ├── at.js ├── fixtures ├── capture-fixture.js ├── generate-parse-fixture.js ├── internal-error.js ├── internal-then.js ├── long-stack-traces.js ├── nested-errors.js ├── parse-fixture.json └── produce-long-stack-traces.js ├── internals.js ├── long-stack-traces.js ├── node-modules.js ├── parse-line-fixtures.js └── test.js /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | indent_style = space 5 | end_of_line = lf 6 | charset = utf-8 7 | trim_trailing_whitespace = true 8 | insert_final_newline = true 9 | 10 | [{package.json,*.yml}] 11 | indent_style = space 12 | indent_size = 2 13 | 14 | [*.md] 15 | trim_trailing_whitespace = false 16 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto 2 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: [push, pull_request] 4 | 5 | jobs: 6 | build: 7 | strategy: 8 | matrix: 9 | node-version: [12.x, 14.x, 16.x, 17.x] 10 | platform: 11 | - os: ubuntu-latest 12 | shell: bash 13 | - os: macos-latest 14 | shell: bash 15 | - os: windows-latest 16 | shell: bash 17 | - os: windows-latest 18 | shell: powershell 19 | fail-fast: false 20 | 21 | runs-on: ${{ matrix.platform.os }} 22 | defaults: 23 | run: 24 | shell: ${{ matrix.platform.shell }} 25 | 26 | steps: 27 | - name: Checkout Repository 28 | uses: actions/checkout@v1.1.0 29 | 30 | - name: Use Nodejs ${{ matrix.node-version }} 31 | uses: actions/setup-node@v1 32 | with: 33 | node-version: ${{ matrix.node-version }} 34 | 35 | - name: Install dependencies 36 | run: npm install 37 | 38 | - name: Run Tests 39 | run: npm test -- -c -t0 40 | -------------------------------------------------------------------------------- /.github/workflows/commit-if-modified.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | git config --global user.email "$1" 3 | shift 4 | git config --global user.name "$1" 5 | shift 6 | message="$1" 7 | shift 8 | if [ $(git status --porcelain "$@" | egrep '^ M' | wc -l) -gt 0 ]; then 9 | git add "$@" 10 | git commit -m "$message" 11 | git push || git pull --rebase 12 | git push 13 | fi 14 | -------------------------------------------------------------------------------- /.github/workflows/copyright-year.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | dir=${1:-$PWD} 3 | dates=($(git log --date=format:%Y --pretty=format:'%ad' --reverse | sort | uniq)) 4 | if [ "${#dates[@]}" -eq 1 ]; then 5 | datestr="${dates}" 6 | else 7 | datestr="${dates}-${dates[${#dates[@]}-1]}" 8 | fi 9 | 10 | stripDate='s/^((.*)Copyright\b(.*?))((?:,\s*)?(([0-9]{4}\s*-\s*[0-9]{4})|(([0-9]{4},\s*)*[0-9]{4})))(?:,)?\s*(.*)\n$/$1$9\n/g' 11 | addDate='s/^.*Copyright(?:\s*\(c\))? /Copyright \(c\) '$datestr' /g' 12 | for l in $dir/LICENSE*; do 13 | perl -pi -e "$stripDate" $l 14 | perl -pi -e "$addDate" $l 15 | done 16 | -------------------------------------------------------------------------------- /.github/workflows/isaacs-makework.yml: -------------------------------------------------------------------------------- 1 | name: "various tidying up tasks to silence nagging" 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | workflow_dispatch: 8 | 9 | jobs: 10 | makework: 11 | runs-on: ubuntu-latest 12 | steps: 13 | - uses: actions/checkout@v2 14 | with: 15 | fetch-depth: 0 16 | - name: Use Node.js 17 | uses: actions/setup-node@v2.1.4 18 | with: 19 | node-version: 16.x 20 | - name: put repo in package.json 21 | run: node .github/workflows/package-json-repo.js 22 | - name: check in package.json if modified 23 | run: | 24 | bash -x .github/workflows/commit-if-modified.sh \ 25 | "package-json-repo-bot@example.com" \ 26 | "package.json Repo Bot" \ 27 | "chore: add repo to package.json" \ 28 | package.json package-lock.json 29 | - name: put all dates in license copyright line 30 | run: bash .github/workflows/copyright-year.sh 31 | - name: check in licenses if modified 32 | run: | 33 | bash .github/workflows/commit-if-modified.sh \ 34 | "license-year-bot@example.com" \ 35 | "License Year Bot" \ 36 | "chore: add copyright year to license" \ 37 | LICENSE* 38 | -------------------------------------------------------------------------------- /.github/workflows/package-json-repo.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const pf = require.resolve(`${process.cwd()}/package.json`) 4 | const pj = require(pf) 5 | 6 | if (!pj.repository && process.env.GITHUB_REPOSITORY) { 7 | const fs = require('fs') 8 | const server = process.env.GITHUB_SERVER_URL || 'https://github.com' 9 | const repo = `${server}/${process.env.GITHUB_REPOSITORY}` 10 | pj.repository = repo 11 | const json = fs.readFileSync(pf, 'utf8') 12 | const match = json.match(/^\s*\{[\r\n]+([ \t]*)"/) 13 | const indent = match[1] 14 | const output = JSON.stringify(pj, null, indent || 2) + '\n' 15 | fs.writeFileSync(pf, output) 16 | } 17 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | .nyc_output 4 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2016-2023 Isaac Z. Schlueter , James Talmage (github.com/jamestalmage), and Contributors 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const escapeStringRegexp = require('escape-string-regexp'); 4 | 5 | const cwd = typeof process === 'object' && process && typeof process.cwd === 'function' 6 | ? process.cwd() 7 | : '.' 8 | 9 | const natives = [].concat( 10 | require('module').builtinModules, 11 | 'bootstrap_node', 12 | 'node', 13 | ).map(n => new RegExp(`(?:\\((?:node:)?${n}(?:\\.js)?:\\d+:\\d+\\)$|^\\s*at (?:node:)?${n}(?:\\.js)?:\\d+:\\d+$)`)); 14 | 15 | natives.push( 16 | /\((?:node:)?internal\/[^:]+:\d+:\d+\)$/, 17 | /\s*at (?:node:)?internal\/[^:]+:\d+:\d+$/, 18 | /\/\.node-spawn-wrap-\w+-\w+\/node:\d+:\d+\)?$/ 19 | ); 20 | 21 | class StackUtils { 22 | constructor (opts) { 23 | opts = { 24 | ignoredPackages: [], 25 | ...opts 26 | }; 27 | 28 | if ('internals' in opts === false) { 29 | opts.internals = StackUtils.nodeInternals(); 30 | } 31 | 32 | if ('cwd' in opts === false) { 33 | opts.cwd = cwd 34 | } 35 | 36 | this._cwd = opts.cwd.replace(/\\/g, '/'); 37 | this._internals = [].concat( 38 | opts.internals, 39 | ignoredPackagesRegExp(opts.ignoredPackages) 40 | ); 41 | 42 | this._wrapCallSite = opts.wrapCallSite || false; 43 | } 44 | 45 | static nodeInternals () { 46 | return [...natives]; 47 | } 48 | 49 | clean (stack, indent = 0) { 50 | indent = ' '.repeat(indent); 51 | 52 | if (!Array.isArray(stack)) { 53 | stack = stack.split('\n'); 54 | } 55 | 56 | if (!(/^\s*at /.test(stack[0])) && (/^\s*at /.test(stack[1]))) { 57 | stack = stack.slice(1); 58 | } 59 | 60 | let outdent = false; 61 | let lastNonAtLine = null; 62 | const result = []; 63 | 64 | stack.forEach(st => { 65 | st = st.replace(/\\/g, '/'); 66 | 67 | if (this._internals.some(internal => internal.test(st))) { 68 | return; 69 | } 70 | 71 | const isAtLine = /^\s*at /.test(st); 72 | 73 | if (outdent) { 74 | st = st.trimEnd().replace(/^(\s+)at /, '$1'); 75 | } else { 76 | st = st.trim(); 77 | if (isAtLine) { 78 | st = st.slice(3); 79 | } 80 | } 81 | 82 | st = st.replace(`${this._cwd}/`, ''); 83 | 84 | if (st) { 85 | if (isAtLine) { 86 | if (lastNonAtLine) { 87 | result.push(lastNonAtLine); 88 | lastNonAtLine = null; 89 | } 90 | 91 | result.push(st); 92 | } else { 93 | outdent = true; 94 | lastNonAtLine = st; 95 | } 96 | } 97 | }); 98 | 99 | return result.map(line => `${indent}${line}\n`).join(''); 100 | } 101 | 102 | captureString (limit, fn = this.captureString) { 103 | if (typeof limit === 'function') { 104 | fn = limit; 105 | limit = Infinity; 106 | } 107 | 108 | const {stackTraceLimit} = Error; 109 | if (limit) { 110 | Error.stackTraceLimit = limit; 111 | } 112 | 113 | const obj = {}; 114 | 115 | Error.captureStackTrace(obj, fn); 116 | const {stack} = obj; 117 | Error.stackTraceLimit = stackTraceLimit; 118 | 119 | return this.clean(stack); 120 | } 121 | 122 | capture (limit, fn = this.capture) { 123 | if (typeof limit === 'function') { 124 | fn = limit; 125 | limit = Infinity; 126 | } 127 | 128 | const {prepareStackTrace, stackTraceLimit} = Error; 129 | Error.prepareStackTrace = (obj, site) => { 130 | if (this._wrapCallSite) { 131 | return site.map(this._wrapCallSite); 132 | } 133 | 134 | return site; 135 | }; 136 | 137 | if (limit) { 138 | Error.stackTraceLimit = limit; 139 | } 140 | 141 | const obj = {}; 142 | Error.captureStackTrace(obj, fn); 143 | const { stack } = obj; 144 | Object.assign(Error, {prepareStackTrace, stackTraceLimit}); 145 | 146 | return stack; 147 | } 148 | 149 | at (fn = this.at) { 150 | const [site] = this.capture(1, fn); 151 | 152 | if (!site) { 153 | return {}; 154 | } 155 | 156 | const res = { 157 | line: site.getLineNumber(), 158 | column: site.getColumnNumber() 159 | }; 160 | 161 | setFile(res, site.getFileName(), this._cwd); 162 | 163 | if (site.isConstructor()) { 164 | Object.defineProperty(res, 'constructor', { 165 | value: true, 166 | configurable: true, 167 | }); 168 | } 169 | 170 | if (site.isEval()) { 171 | res.evalOrigin = site.getEvalOrigin(); 172 | } 173 | 174 | // Node v10 stopped with the isNative() on callsites, apparently 175 | /* istanbul ignore next */ 176 | if (site.isNative()) { 177 | res.native = true; 178 | } 179 | 180 | let typename; 181 | try { 182 | typename = site.getTypeName(); 183 | } catch (_) { 184 | } 185 | 186 | if (typename && typename !== 'Object' && typename !== '[object Object]') { 187 | res.type = typename; 188 | } 189 | 190 | const fname = site.getFunctionName(); 191 | if (fname) { 192 | res.function = fname; 193 | } 194 | 195 | const meth = site.getMethodName(); 196 | if (meth && fname !== meth) { 197 | res.method = meth; 198 | } 199 | 200 | return res; 201 | } 202 | 203 | parseLine (line) { 204 | const match = line && line.match(re); 205 | if (!match) { 206 | return null; 207 | } 208 | 209 | const ctor = match[1] === 'new'; 210 | let fname = match[2]; 211 | const evalOrigin = match[3]; 212 | const evalFile = match[4]; 213 | const evalLine = Number(match[5]); 214 | const evalCol = Number(match[6]); 215 | let file = match[7]; 216 | const lnum = match[8]; 217 | const col = match[9]; 218 | const native = match[10] === 'native'; 219 | const closeParen = match[11] === ')'; 220 | let method; 221 | 222 | const res = {}; 223 | 224 | if (lnum) { 225 | res.line = Number(lnum); 226 | } 227 | 228 | if (col) { 229 | res.column = Number(col); 230 | } 231 | 232 | if (closeParen && file) { 233 | // make sure parens are balanced 234 | // if we have a file like "asdf) [as foo] (xyz.js", then odds are 235 | // that the fname should be += " (asdf) [as foo]" and the file 236 | // should be just "xyz.js" 237 | // walk backwards from the end to find the last unbalanced ( 238 | let closes = 0; 239 | for (let i = file.length - 1; i > 0; i--) { 240 | if (file.charAt(i) === ')') { 241 | closes++; 242 | } else if (file.charAt(i) === '(' && file.charAt(i - 1) === ' ') { 243 | closes--; 244 | if (closes === -1 && file.charAt(i - 1) === ' ') { 245 | const before = file.slice(0, i - 1); 246 | const after = file.slice(i + 1); 247 | file = after; 248 | fname += ` (${before}`; 249 | break; 250 | } 251 | } 252 | } 253 | } 254 | 255 | if (fname) { 256 | const methodMatch = fname.match(methodRe); 257 | if (methodMatch) { 258 | fname = methodMatch[1]; 259 | method = methodMatch[2]; 260 | } 261 | } 262 | 263 | setFile(res, file, this._cwd); 264 | 265 | if (ctor) { 266 | Object.defineProperty(res, 'constructor', { 267 | value: true, 268 | configurable: true, 269 | }); 270 | } 271 | 272 | if (evalOrigin) { 273 | res.evalOrigin = evalOrigin; 274 | res.evalLine = evalLine; 275 | res.evalColumn = evalCol; 276 | res.evalFile = evalFile && evalFile.replace(/\\/g, '/'); 277 | } 278 | 279 | if (native) { 280 | res.native = true; 281 | } 282 | 283 | if (fname) { 284 | res.function = fname; 285 | } 286 | 287 | if (method && fname !== method) { 288 | res.method = method; 289 | } 290 | 291 | return res; 292 | } 293 | } 294 | 295 | function setFile (result, filename, cwd) { 296 | if (filename) { 297 | filename = filename.replace(/\\/g, '/'); 298 | if (filename.startsWith(`${cwd}/`)) { 299 | filename = filename.slice(cwd.length + 1); 300 | } 301 | 302 | result.file = filename; 303 | } 304 | } 305 | 306 | function ignoredPackagesRegExp(ignoredPackages) { 307 | if (ignoredPackages.length === 0) { 308 | return []; 309 | } 310 | 311 | const packages = ignoredPackages.map(mod => escapeStringRegexp(mod)); 312 | 313 | return new RegExp(`[\/\\\\]node_modules[\/\\\\](?:${packages.join('|')})[\/\\\\][^:]+:\\d+:\\d+`) 314 | } 315 | 316 | const re = new RegExp( 317 | '^' + 318 | // Sometimes we strip out the ' at' because it's noisy 319 | '(?:\\s*at )?' + 320 | // $1 = ctor if 'new' 321 | '(?:(new) )?' + 322 | // $2 = function name (can be literally anything) 323 | // May contain method at the end as [as xyz] 324 | '(?:(.*?) \\()?' + 325 | // (eval at (file.js:1:1), 326 | // $3 = eval origin 327 | // $4:$5:$6 are eval file/line/col, but not normally reported 328 | '(?:eval at ([^ ]+) \\((.+?):(\\d+):(\\d+)\\), )?' + 329 | // file:line:col 330 | // $7:$8:$9 331 | // $10 = 'native' if native 332 | '(?:(.+?):(\\d+):(\\d+)|(native))' + 333 | // maybe close the paren, then end 334 | // if $11 is ), then we only allow balanced parens in the filename 335 | // any imbalance is placed on the fname. This is a heuristic, and 336 | // bound to be incorrect in some edge cases. The bet is that 337 | // having weird characters in method names is more common than 338 | // having weird characters in filenames, which seems reasonable. 339 | '(\\)?)$' 340 | ); 341 | 342 | const methodRe = /^(.*?) \[as (.*?)\]$/; 343 | 344 | module.exports = StackUtils; 345 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stack-utils", 3 | "version": "2.0.6", 4 | "description": "Captures and cleans stack traces", 5 | "license": "MIT", 6 | "repository": "tapjs/stack-utils", 7 | "author": { 8 | "name": "James Talmage", 9 | "email": "james@talmage.io", 10 | "url": "github.com/jamestalmage" 11 | }, 12 | "engines": { 13 | "node": ">=10" 14 | }, 15 | "scripts": { 16 | "test": "tap", 17 | "preversion": "npm test", 18 | "postversion": "npm publish", 19 | "prepublishOnly": "git push origin --follow-tags" 20 | }, 21 | "tap": { 22 | "check-coverage": true 23 | }, 24 | "files": [ 25 | "index.js" 26 | ], 27 | "dependencies": { 28 | "escape-string-regexp": "^2.0.0" 29 | }, 30 | "devDependencies": { 31 | "bluebird": "^3.7.2", 32 | "coveralls": "^3.0.9", 33 | "nested-error-stacks": "^2.1.0", 34 | "pify": "^4.0.1", 35 | "q": "^1.5.1", 36 | "source-map-support": "^0.5.20", 37 | "tap": "^16.3.0" 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # stack-utils 2 | 3 | > Captures and cleans stack traces. 4 | 5 | [![Linux Build](https://travis-ci.org/tapjs/stack-utils.svg?branch=master)](https://travis-ci.org/tapjs/stack-utils) [![Build status](https://ci.appveyor.com/api/projects/status/fb9i157knoixe3iq/branch/master?svg=true)](https://ci.appveyor.com/project/jamestalmage/stack-utils-oiw96/branch/master) [![Coverage](https://coveralls.io/repos/tapjs/stack-utils/badge.svg?branch=master&service=github)](https://coveralls.io/github/tapjs/stack-utils?branch=master) 6 | 7 | 8 | Extracted from `lib/stack.js` in the [`node-tap` project](https://github.com/tapjs/node-tap) 9 | 10 | ## Install 11 | 12 | ``` 13 | $ npm install --save stack-utils 14 | ``` 15 | 16 | 17 | ## Usage 18 | 19 | ```js 20 | const StackUtils = require('stack-utils'); 21 | const stack = new StackUtils({cwd: process.cwd(), internals: StackUtils.nodeInternals()}); 22 | 23 | console.log(stack.clean(new Error().stack)); 24 | // outputs a beautified stack trace 25 | ``` 26 | 27 | 28 | ## API 29 | 30 | 31 | ### new StackUtils([options]) 32 | 33 | Creates a new `stackUtils` instance. 34 | 35 | #### options 36 | 37 | ##### internals 38 | 39 | Type: `array` of `RegularExpression`s 40 | 41 | A set of regular expressions that match internal stack stack trace lines which should be culled from the stack trace. 42 | The default is `StackUtils.nodeInternals()`, this can be disabled by setting `[]` or appended using 43 | `StackUtils.nodeInternals().concat(additionalRegExp)`. See also `ignoredPackages`. 44 | 45 | ##### ignoredPackages 46 | 47 | Type: `array` of `string`s 48 | 49 | An array of npm modules to be culled from the stack trace. This list will mapped to regular 50 | expressions and merged with the `internals`. 51 | 52 | Default `''`. 53 | 54 | ##### cwd 55 | 56 | Type: `string` 57 | 58 | The path to the current working directory. File names in the stack trace will be shown relative to this directory. 59 | 60 | ##### wrapCallSite 61 | 62 | Type: `function(CallSite)` 63 | 64 | A mapping function for manipulating CallSites before processing. The first argument is a CallSite instance, and the function should return a modified CallSite. This is useful for providing source map support. 65 | 66 | 67 | ### StackUtils.nodeInternals() 68 | 69 | Returns an array of regular expressions that be used to cull lines from the stack trace that reference common Node.js internal files. 70 | 71 | 72 | ### stackUtils.clean(stack, indent = 0) 73 | 74 | Cleans up a stack trace by deleting any lines that match the `internals` passed to the constructor, and shortening file names relative to `cwd`. 75 | 76 | Returns a `string` with the cleaned up stack (always terminated with a `\n` newline character). 77 | Spaces at the start of each line are trimmed, indentation can be added by setting `indent` to the desired number of spaces. 78 | 79 | #### stack 80 | 81 | *Required* 82 | Type: `string` or an `array` of `string`s 83 | 84 | 85 | ### stackUtils.capture([limit], [startStackFunction]) 86 | 87 | Captures the current stack trace, returning an array of `CallSite`s. There are good overviews of the available CallSite methods [here](https://github.com/v8/v8/wiki/Stack%20Trace%20API#customizing-stack-traces), and [here](https://github.com/sindresorhus/callsites#api). 88 | 89 | #### limit 90 | 91 | Type: `number` 92 | Default: `Infinity` 93 | 94 | Limits the number of lines returned by dropping all lines in excess of the limit. This removes lines from the stack trace. 95 | 96 | #### startStackFunction 97 | 98 | Type: `function` 99 | 100 | The function where the stack trace should start. The first line of the stack trace will be the function that called `startStackFunction`. This removes lines from the end of the stack trace. 101 | 102 | 103 | ### stackUtils.captureString([limit], [startStackFunction]) 104 | 105 | Captures the current stack trace, cleans it using `stackUtils.clean(stack)`, and returns a string with the cleaned stack trace. It takes the same arguments as `stackUtils.capture`. 106 | 107 | 108 | ### stackUtils.at([startStackFunction]) 109 | 110 | Captures the first line of the stack trace (or the first line after `startStackFunction` if supplied), and returns a `CallSite` like object that is serialization friendly (properties are actual values instead of getter functions). 111 | 112 | The available properties are: 113 | 114 | - `line`: `number` 115 | - `column`: `number` 116 | - `file`: `string` 117 | - `constructor`: `boolean` 118 | - `evalOrigin`: `string` 119 | - `native`: `boolean` 120 | - `type`: `string` 121 | - `function`: `string` 122 | - `method`: `string` 123 | 124 | ### stackUtils.parseLine(line) 125 | 126 | Parses a `string` (which should be a single line from a stack trace), and generates an object with the following properties: 127 | 128 | - `line`: `number` 129 | - `column`: `number` 130 | - `file`: `string` 131 | - `constructor`: `boolean` 132 | - `evalOrigin`: `string` 133 | - `evalLine`: `number` 134 | - `evalColumn`: `number` 135 | - `evalFile`: `string` 136 | - `native`: `boolean` 137 | - `function`: `string` 138 | - `method`: `string` 139 | 140 | 141 | ## License 142 | 143 | MIT © [Isaac Z. Schlueter](http://github.com/isaacs), [James Talmage](http://github.com/jamestalmage) 144 | -------------------------------------------------------------------------------- /test/_utils.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | 5 | module.exports.join = join; 6 | module.exports.fixtureDir = path.join(__dirname, 'fixtures'); 7 | 8 | function join(...args) { 9 | return [].concat(...args, '').join('\n'); 10 | } 11 | 12 | if (module === require.main) { 13 | require('tap').pass('this is fine'); 14 | } 15 | -------------------------------------------------------------------------------- /test/at.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // some capture edge cases not already covered by other tests 3 | 4 | const StackUtils = require('../'); 5 | const t = require('tap'); 6 | 7 | const stack = new StackUtils(); 8 | 9 | // a capture with no function, not much to it, actually 10 | const base = __filename.slice(process.cwd().length + 1).replace(/\\/g, '/'); 11 | t.match(stack.at(), { line: Number, column: Number, file: base }); 12 | 13 | // a capture from a native site 14 | const arr = [ 0 ]; 15 | const captures = arr.map(function xyz () { 16 | return stack.at(xyz); 17 | }); 18 | t.match(captures, [ { 19 | type: 'Array', 20 | function: 'map' 21 | } ]); 22 | -------------------------------------------------------------------------------- /test/fixtures/capture-fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | class CaptureFixture { 4 | constructor (stack) { 5 | this.stack = stack; 6 | } 7 | 8 | redirect1 (method, ...args) { 9 | return this[method](...args); 10 | } 11 | 12 | redirect2 (method, ...args) { 13 | return this[method](...args); 14 | } 15 | 16 | const (method, ...args) { 17 | const self = this; 18 | class Constructor { 19 | constructor () { 20 | this.val = self[method](...args); 21 | } 22 | } 23 | 24 | return new Constructor().val; 25 | } 26 | 27 | obj (methodName, method, ...args) { 28 | const obj = { 29 | [methodName]: () => this[method](...args) 30 | }; 31 | 32 | return obj[methodName](); 33 | } 34 | 35 | eval (method, ...args) { 36 | const self = this; 37 | 38 | return eval('self[method](...args)'); 39 | } 40 | 41 | error (message) { 42 | return new Error(message); 43 | } 44 | } 45 | 46 | CaptureFixture.prototype.call = function (method, ...args) { 47 | return this.stack[method](...args); 48 | }; 49 | 50 | module.exports = CaptureFixture; 51 | -------------------------------------------------------------------------------- /test/fixtures/generate-parse-fixture.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | // Run this file to re-generate the parse fixture json after changes. 3 | 4 | const lines = new Set(); 5 | const basename = require('path').basename(__filename); 6 | const lineRE = new RegExp('\\b' + basename + ':([0-9]+):([0-9]+)\\b', 'g'); 7 | const capture = e => e.stack.split('\n').forEach(line => { 8 | if (line === 'Error: ok') 9 | return; 10 | 11 | lines.add( 12 | line 13 | .split(__dirname).join('__dirname') 14 | .replace(lineRE, basename + ':420:69') 15 | ); 16 | }); 17 | 18 | const done = _ => { 19 | const StackUtils = require('../..'); 20 | const stack = new StackUtils(); 21 | const obj = Object.create(null); 22 | lines.forEach(line => { 23 | obj[line] = stack.parseLine(line); 24 | }); 25 | const fs = require('fs'); 26 | const json = JSON.stringify(obj, null, 2) + '\n'; 27 | fs.writeFileSync(__dirname + '/parse-fixture.json', json); 28 | }; 29 | 30 | { 31 | const s = Symbol('foo'); 32 | const o = { [s] () { throw new Error('ok'); } }; 33 | try { 34 | o[s](); 35 | } catch (e) { 36 | capture(e); 37 | } 38 | } 39 | 40 | { 41 | const o = { ['asdf ][)( \u0000\u0001\u0002\u0003\u001b[44;37m foo'] () { throw new Error('ok'); } }; 42 | try { 43 | o['asdf ][)( \u0000\u0001\u0002\u0003\u001b[44;37m foo'](); 44 | } catch (e) { 45 | capture(e); 46 | } 47 | } 48 | 49 | { 50 | const s = 'asdf (' + __filename + ':1:9999)'; 51 | const o = { [s] () { throw new Error('ok'); } }; 52 | try { 53 | o[s](); 54 | } catch (e) { 55 | capture(e); 56 | } 57 | } 58 | 59 | { 60 | const s = 'eval'; 61 | const o = { [s] () { throw new Error('ok'); } }; 62 | try { 63 | eval('o[s]()'); 64 | } catch (e) { 65 | capture(e); 66 | } 67 | } 68 | 69 | { 70 | const vm = require('vm'); 71 | const code = 'function fooeval () { eval("o[s]()") }; fooeval()'; 72 | const s = 'a (s) d [f]'; 73 | const o = { [s] () { throw new Error('ok'); } }; 74 | try { 75 | vm.runInNewContext(code, {o, s}, { filename: 'a file with eval .js' }); 76 | } catch (e) { 77 | capture(e); 78 | } 79 | } 80 | 81 | { 82 | const vm = require('vm'); 83 | const code = 'eval("o[s]()")'; 84 | const s = 'a (s) d [f]'; 85 | const o = { [s] () { throw new Error('ok'); } }; 86 | try { 87 | vm.runInNewContext(code, {o, s}, { filename: 'a file with eval .js' }); 88 | } catch (e) { 89 | capture(e); 90 | } 91 | } 92 | 93 | { 94 | const s = 'function ctor (file.js:1:2) '; 95 | const o = { [s] () { throw new Error('ok'); } }; 96 | try { 97 | new Function('o', 's', 'o[s]()')(o, s); 98 | } catch (e) { 99 | capture(e); 100 | } 101 | } 102 | 103 | { 104 | const s = Symbol.iterator; 105 | const o = { [s] () { throw new Error('ok'); } }; 106 | try { 107 | new Function('o', 's', 'o[s]()')(o, s); 108 | } catch (e) { 109 | capture(e); 110 | } 111 | } 112 | 113 | { 114 | const s = Symbol.iterator; 115 | const o = new class Classy { [s] () { throw new Error('ok'); } }; 116 | try { 117 | new Function('o', 's', 'o[s]()')(o, s); 118 | } catch (e) { 119 | capture(e); 120 | } 121 | } 122 | 123 | { 124 | const s = Symbol('some (weird) []'); 125 | const o = new class Classy { [s] () { throw new Error('ok'); } }; 126 | try { 127 | const x = o[s]; 128 | x(); 129 | } catch (e) { 130 | capture(e); 131 | } 132 | } 133 | 134 | { 135 | const s = Symbol('some (weird) []'); 136 | const o = new class Classy { [s] () { throw new Error('ok'); } }; 137 | try { 138 | const x = { foo: o[s] }; 139 | x.foo(); 140 | } catch (e) { 141 | capture(e); 142 | } 143 | } 144 | 145 | { 146 | const s = Symbol('some (weird) []'); 147 | const o = new class Classy { [s] () { throw new Error('ok'); } }; 148 | try { 149 | const x = new class OtherClass { constructor() { this.foo = o[s]; } }; 150 | x.foo(); 151 | } catch (e) { 152 | capture(e); 153 | } 154 | } 155 | 156 | { 157 | const vm = require('vm'); 158 | const o = { ['a (w) []'] () { throw new Error('ok'); } }; 159 | try { 160 | vm.runInNewContext('o["a (w) []"]()', { o: o }); 161 | } catch (e) { 162 | capture(e); 163 | } 164 | } 165 | 166 | { 167 | const vm = require('vm'); 168 | const o = { ['a (w) []'] () { throw new Error('ok'); } }; 169 | try { 170 | vm.runInNewContext( 171 | 'function x () { o["a (w) []"]() }\n' + 172 | 'x()', { o: o }, { 173 | filename: ' f[i](l:.js:1:2) ' 174 | }); 175 | } catch (e) { 176 | capture(e); 177 | } 178 | } 179 | 180 | { 181 | class Foo { 182 | constructor () { 183 | throw new Error('ok'); 184 | } 185 | } 186 | try { 187 | new Foo(); 188 | } catch (e) { 189 | capture(e); 190 | } 191 | } 192 | 193 | { 194 | class Foo { 195 | constructor (n) { 196 | this.n = n; 197 | throw new Error('ok'); 198 | } 199 | } 200 | const arr = [1, 2, 3]; 201 | try { 202 | arr.map(n => new Foo(n)); 203 | } catch (e) { 204 | capture(e); 205 | } 206 | } 207 | 208 | done(); 209 | -------------------------------------------------------------------------------- /test/fixtures/internal-error.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | const NestedError = require('nested-error-stacks'); 3 | const util = require('util'); 4 | 5 | function InternalError(message, nested) { 6 | NestedError.call(this, message, nested); 7 | } 8 | 9 | util.inherits(InternalError, NestedError); 10 | InternalError.prototype.name = 'InternalError'; 11 | 12 | module.exports = function (cb, err) { 13 | setTimeout(bound.bind(null, cb, err), 0); 14 | }; 15 | 16 | function bound(cb, err) { 17 | cb(new InternalError('internal' + (err ? ': ' + err.message : ''), err)); 18 | } 19 | -------------------------------------------------------------------------------- /test/fixtures/internal-then.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | module.exports = function internalLibraryOuterFn(then) { 4 | return global.InternalPromise.resolve().then(function internalLibraryInnerFn() { 5 | return global.InternalPromise.resolve().then(then); 6 | }); 7 | }; 8 | 9 | module.exports.reject = function internalLibraryOuterReject() { 10 | return global.InternalPromise.resolve().then(function internalLibraryInnerReject() { 11 | return global.InternalPromise.reject(new Error('inner')).catch(function (e) { 12 | return e.stack; 13 | }); 14 | }); 15 | }; 16 | -------------------------------------------------------------------------------- /test/fixtures/long-stack-traces.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const Q = require('q'); 4 | Q.longStackSupport = true; 5 | global.InternalPromise = Q; 6 | module.exports.q = require('./produce-long-stack-traces'); 7 | 8 | const longStackTracePath = require.resolve('./produce-long-stack-traces'); 9 | const internalThen = require.resolve('./internal-then'); 10 | delete require.cache[longStackTracePath]; 11 | delete require.cache[internalThen]; 12 | 13 | const bluebird = require('bluebird'); 14 | bluebird.config({longStackTraces: true}); 15 | global.InternalPromise = bluebird; 16 | module.exports.bluebird = require('./produce-long-stack-traces'); 17 | -------------------------------------------------------------------------------- /test/fixtures/nested-errors.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const NestedError = require('nested-error-stacks'); 4 | const util = require('util'); 5 | const internal = require('./internal-error'); 6 | 7 | function foo(cb) { 8 | bar(function nested(err) { 9 | cb(new FooError(`foo${err.message}`, err)); 10 | }); 11 | } 12 | 13 | function bar(cb) { 14 | internal(function moreNested(err) { 15 | cb(new BarError(`bar: ${err.message}`, err)); 16 | }); 17 | } 18 | 19 | function FooError(message, nested) { 20 | NestedError.call(this, message, nested); 21 | } 22 | 23 | util.inherits(FooError, NestedError); 24 | FooError.prototype.name = 'FooError'; 25 | 26 | function BarError(message, nested) { 27 | NestedError.call(this, message, nested); 28 | } 29 | 30 | util.inherits(BarError, NestedError); 31 | BarError.prototype.name = 'BarError'; 32 | 33 | module.exports.top = function(cb) { 34 | internal(function (err) { 35 | cb(err.stack); 36 | }, new Error('baz')); 37 | }; 38 | 39 | module.exports.middle = function (cb) { 40 | internal(function (err) { 41 | cb(new FooError('foo', err).stack); 42 | }, new Error('bar')); 43 | }; 44 | 45 | module.exports.bottom = function (cb) { 46 | foo(function (err) { 47 | cb(err.stack); 48 | }); 49 | }; 50 | -------------------------------------------------------------------------------- /test/fixtures/parse-fixture.json: -------------------------------------------------------------------------------- 1 | { 2 | " at Object.[foo] (__dirname/generate-parse-fixture.js:420:69)": { 3 | "line": 420, 4 | "column": 69, 5 | "file": "__dirname/generate-parse-fixture.js", 6 | "function": "Object.[foo]" 7 | }, 8 | " at Object. (__dirname/generate-parse-fixture.js:420:69)": { 9 | "line": 420, 10 | "column": 69, 11 | "file": "__dirname/generate-parse-fixture.js", 12 | "function": "Object." 13 | }, 14 | " at Module._compile (module.js:571:32)": { 15 | "line": 571, 16 | "column": 32, 17 | "file": "module.js", 18 | "function": "Module._compile" 19 | }, 20 | " at Object.Module._extensions..js (module.js:580:10)": { 21 | "line": 580, 22 | "column": 10, 23 | "file": "module.js", 24 | "function": "Object.Module._extensions..js" 25 | }, 26 | " at Module.load (module.js:488:32)": { 27 | "line": 488, 28 | "column": 32, 29 | "file": "module.js", 30 | "function": "Module.load" 31 | }, 32 | " at tryModuleLoad (module.js:447:12)": { 33 | "line": 447, 34 | "column": 12, 35 | "file": "module.js", 36 | "function": "tryModuleLoad" 37 | }, 38 | " at Function.Module._load (module.js:439:3)": { 39 | "line": 439, 40 | "column": 3, 41 | "file": "module.js", 42 | "function": "Function.Module._load" 43 | }, 44 | " at Module.runMain (module.js:605:10)": { 45 | "line": 605, 46 | "column": 10, 47 | "file": "module.js", 48 | "function": "Module.runMain" 49 | }, 50 | " at run (bootstrap_node.js:418:7)": { 51 | "line": 418, 52 | "column": 7, 53 | "file": "bootstrap_node.js", 54 | "function": "run" 55 | }, 56 | " at startup (bootstrap_node.js:139:9)": { 57 | "line": 139, 58 | "column": 9, 59 | "file": "bootstrap_node.js", 60 | "function": "startup" 61 | }, 62 | " at Object.asdf ][)( \u0000\u0001\u0002\u0003\u001b[44;37m foo (__dirname/generate-parse-fixture.js:420:69)": { 63 | "line": 420, 64 | "column": 69, 65 | "file": "__dirname/generate-parse-fixture.js", 66 | "function": "Object.asdf ][)( \u0000\u0001\u0002\u0003\u001b[44;37m foo" 67 | }, 68 | " at Object.asdf (__dirname/generate-parse-fixture.js:420:69) (__dirname/generate-parse-fixture.js:420:69)": { 69 | "line": 420, 70 | "column": 69, 71 | "file": "__dirname/generate-parse-fixture.js", 72 | "function": "Object.asdf (__dirname/generate-parse-fixture.js:420:69)" 73 | }, 74 | " at Object.eval (__dirname/generate-parse-fixture.js:420:69)": { 75 | "line": 420, 76 | "column": 69, 77 | "file": "__dirname/generate-parse-fixture.js", 78 | "function": "Object.eval" 79 | }, 80 | " at eval (eval at (__dirname/generate-parse-fixture.js:420:69), :1:5)": { 81 | "line": 1, 82 | "column": 5, 83 | "file": "", 84 | "evalOrigin": "", 85 | "evalLine": 420, 86 | "evalColumn": 69, 87 | "evalFile": "__dirname/generate-parse-fixture.js", 88 | "function": "eval" 89 | }, 90 | " at Object.a (s) d [f] (__dirname/generate-parse-fixture.js:420:69)": { 91 | "line": 420, 92 | "column": 69, 93 | "file": "__dirname/generate-parse-fixture.js", 94 | "function": "Object.a (s) d [f]" 95 | }, 96 | " at eval (eval at fooeval (a file with eval .js:1:23), :1:5)": { 97 | "line": 1, 98 | "column": 5, 99 | "file": "", 100 | "evalOrigin": "fooeval", 101 | "evalLine": 1, 102 | "evalColumn": 23, 103 | "evalFile": "a file with eval .js", 104 | "function": "eval" 105 | }, 106 | " at fooeval (a file with eval .js:1:23)": { 107 | "line": 1, 108 | "column": 23, 109 | "file": "a file with eval .js", 110 | "function": "fooeval" 111 | }, 112 | " at a file with eval .js:1:41": { 113 | "line": 1, 114 | "column": 41, 115 | "file": "a file with eval .js" 116 | }, 117 | " at ContextifyScript.Script.runInContext (vm.js:32:29)": { 118 | "line": 32, 119 | "column": 29, 120 | "file": "vm.js", 121 | "function": "ContextifyScript.Script.runInContext" 122 | }, 123 | " at ContextifyScript.Script.runInNewContext (vm.js:38:15)": { 124 | "line": 38, 125 | "column": 15, 126 | "file": "vm.js", 127 | "function": "ContextifyScript.Script.runInNewContext" 128 | }, 129 | " at Object.exports.runInNewContext (vm.js:69:17)": { 130 | "line": 69, 131 | "column": 17, 132 | "file": "vm.js", 133 | "function": "Object.exports.runInNewContext" 134 | }, 135 | " at eval (eval at (a file with eval .js:1:1), :1:5)": { 136 | "line": 1, 137 | "column": 5, 138 | "file": "", 139 | "evalOrigin": "", 140 | "evalLine": 1, 141 | "evalColumn": 1, 142 | "evalFile": "a file with eval .js", 143 | "function": "eval" 144 | }, 145 | " at a file with eval .js:1:1": { 146 | "line": 1, 147 | "column": 1, 148 | "file": "a file with eval .js" 149 | }, 150 | " at Object.function ctor (file.js:1:2) (__dirname/generate-parse-fixture.js:420:69)": { 151 | "line": 420, 152 | "column": 69, 153 | "file": "__dirname/generate-parse-fixture.js", 154 | "function": "Object.function ctor (file.js:1:2) " 155 | }, 156 | " at eval (eval at (__dirname/generate-parse-fixture.js:420:69), :3:5)": { 157 | "line": 3, 158 | "column": 5, 159 | "file": "", 160 | "evalOrigin": "", 161 | "evalLine": 420, 162 | "evalColumn": 69, 163 | "evalFile": "__dirname/generate-parse-fixture.js", 164 | "function": "eval" 165 | }, 166 | " at Object.[Symbol.iterator] (__dirname/generate-parse-fixture.js:420:69)": { 167 | "line": 420, 168 | "column": 69, 169 | "file": "__dirname/generate-parse-fixture.js", 170 | "function": "Object.[Symbol.iterator]" 171 | }, 172 | " at Classy.[Symbol.iterator] (__dirname/generate-parse-fixture.js:420:69)": { 173 | "line": 420, 174 | "column": 69, 175 | "file": "__dirname/generate-parse-fixture.js", 176 | "function": "Classy.[Symbol.iterator]" 177 | }, 178 | " at [some (weird) []] (__dirname/generate-parse-fixture.js:420:69)": { 179 | "line": 420, 180 | "column": 69, 181 | "file": "__dirname/generate-parse-fixture.js", 182 | "function": "[some (weird) []]" 183 | }, 184 | " at Object.[some (weird) []] [as foo] (__dirname/generate-parse-fixture.js:420:69)": { 185 | "line": 420, 186 | "column": 69, 187 | "file": "__dirname/generate-parse-fixture.js", 188 | "function": "Object.[some (weird) []]", 189 | "method": "foo" 190 | }, 191 | " at OtherClass.[some (weird) []] [as foo] (__dirname/generate-parse-fixture.js:420:69)": { 192 | "line": 420, 193 | "column": 69, 194 | "file": "__dirname/generate-parse-fixture.js", 195 | "function": "OtherClass.[some (weird) []]", 196 | "method": "foo" 197 | }, 198 | " at Object.a (w) [] (__dirname/generate-parse-fixture.js:420:69)": { 199 | "line": 420, 200 | "column": 69, 201 | "file": "__dirname/generate-parse-fixture.js", 202 | "function": "Object.a (w) []" 203 | }, 204 | " at evalmachine.:1:17": { 205 | "line": 1, 206 | "column": 17, 207 | "file": "evalmachine." 208 | }, 209 | " at x ( f[i](l:.js:1:2) :1:33)": { 210 | "line": 1, 211 | "column": 33, 212 | "file": " f[i](l:.js:1:2) ", 213 | "function": "x" 214 | }, 215 | " at f[i](l:.js:1:2) :2:1": { 216 | "line": 2, 217 | "column": 1, 218 | "file": " f[i](l:.js:1:2) " 219 | }, 220 | " at new Foo (__dirname/generate-parse-fixture.js:420:69)": { 221 | "line": 420, 222 | "column": 69, 223 | "file": "__dirname/generate-parse-fixture.js", 224 | "constructor": true, 225 | "function": "Foo" 226 | }, 227 | " at arr.map.n (__dirname/generate-parse-fixture.js:420:69)": { 228 | "line": 420, 229 | "column": 69, 230 | "file": "__dirname/generate-parse-fixture.js", 231 | "function": "arr.map.n" 232 | }, 233 | " at Array.map (native)": { 234 | "native": true, 235 | "function": "Array.map" 236 | } 237 | } 238 | -------------------------------------------------------------------------------- /test/fixtures/produce-long-stack-traces.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | /* eslint no-undef: 0 */ 3 | const Promise = global.InternalPromise; 4 | const internalThen = require('./internal-then'); 5 | 6 | module.exports = Promise.resolve().then(function outer() { 7 | return Promise.resolve().then(function inner() { 8 | return Promise.resolve().then(function evenMoreInner() { 9 | return Promise.resolve().then(function mostInner() { 10 | a.b.c.d(); 11 | }).catch(function catcher(e) { 12 | return e.stack; 13 | }); 14 | }); 15 | }); 16 | }); 17 | 18 | module.exports.middle = Promise.resolve().then(function outer() { 19 | return Promise.resolve().then(function inner() { 20 | return internalThen(function evenMoreInner() { 21 | return Promise.resolve().then(function mostInner() { 22 | a.b.c.d(); 23 | }).catch(function catcher(e) { 24 | return e.stack; 25 | }); 26 | }); 27 | }); 28 | }); 29 | 30 | module.exports.top = Promise.resolve().then(function outer() { 31 | return Promise.resolve().then(function inner() { 32 | return Promise.resolve().then(function evenMoreInner() { 33 | return Promise.resolve().then(internalThen.reject); 34 | }); 35 | }); 36 | }); 37 | 38 | module.exports.bottom = new Promise(function (resolve) { 39 | setTimeout(internalThen.bind(null, function outer() { 40 | return Promise.resolve().then(function inner() { 41 | return Promise.resolve().then(function evenMoreInner() { 42 | return Promise.resolve().then(function mostInner() { 43 | a.b.c.d(); 44 | }).catch(function catcher(e) { 45 | resolve(e.stack); 46 | }); 47 | }); 48 | }); 49 | }), 0); 50 | }); 51 | -------------------------------------------------------------------------------- /test/internals.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const t = require('tap'); 4 | const StackUtils = require('../'); 5 | 6 | const utils = require('./_utils'); 7 | 8 | const stackUtils = new StackUtils({ cwd: '/home/user' }); 9 | 10 | t.test('removes namespaced internal modules', t => { 11 | const stack = utils.join([ 12 | 'at Test. (test/test.js:99:5)', 13 | 'at TapWrap.runInAsyncScope (node:async_hooks:193:9)', 14 | 'at Object. (test/test.js:94:3)', 15 | 'at Module._compile (node:internal/modules/cjs/loader:1083:30)', 16 | 'at Module.replacementCompile (node_modules/append-transform/index.js:58:13)', 17 | 'at Module._extensions..js (node:internal/modules/cjs/loader:1112:10)', 18 | 'at Object. (node_modules/append-transform/index.js:62:4)', 19 | 'at Module.load (node:internal/modules/cjs/loader:948:32)', 20 | 'at Function.Module._load (node:internal/modules/cjs/loader:789:14)', 21 | 'at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:72:12)', 22 | 'at Module._compile (node:internal/modules/cjs/loader:1083:30)', 23 | 'at Object.Module._extensions..js (node:internal/modules/cjs/loader:1112:10)', 24 | 'at Module.load (node:internal/modules/cjs/loader:948:32)', 25 | 'at Function.Module._load (node:internal/modules/cjs/loader:789:14)', 26 | 'at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:72:12)', 27 | 'at Module._compile (node:internal/modules/cjs/loader:1083:30)', 28 | 'at Object.Module._extensions..js (node:internal/modules/cjs/loader:1112:10)', 29 | 'at Module.load (node:internal/modules/cjs/loader:948:32)', 30 | 'at Function.Module._load (node:internal/modules/cjs/loader:789:14)', 31 | 'at Function.executeUserEntryPoint [as runMain] (node:internal/modules/run_main:72:12)', 32 | 'at node:internal/main/run_main_module:17:47' 33 | ]); 34 | const expectedStack = utils.join([ 35 | 'Test. (test/test.js:99:5)', 36 | 'Object. (test/test.js:94:3)', 37 | 'Module.replacementCompile (node_modules/append-transform/index.js:58:13)', 38 | 'Object. (node_modules/append-transform/index.js:62:4)', 39 | ]) 40 | t.plan(1); 41 | t.equal(stackUtils.clean(stack), expectedStack); 42 | }); 43 | -------------------------------------------------------------------------------- /test/long-stack-traces.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const t = require('tap'); 4 | const StackUtils = require('../'); 5 | const longStackTraces = require('./fixtures/long-stack-traces'); 6 | const pify = require('pify'); 7 | const nestedErrors = pify(require('./fixtures/nested-errors'), require('bluebird')); 8 | 9 | const utils = require('./_utils'); 10 | 11 | function internals() { 12 | return StackUtils.nodeInternals().concat([ 13 | /\/long-stack-traces\.js:\d+:\d+\)?$/, 14 | /\/internal-error\.js:\d+:\d+\)?$/, 15 | /\/internal-then\.js:\d+:\d+\)?$/, 16 | /\/node_modules\// 17 | ]); 18 | } 19 | 20 | const stackUtils = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 21 | 22 | t.test('indents lines after first "From previous event:"', t => { 23 | return longStackTraces.bluebird 24 | .then(stack => { 25 | const cleanedStack = stackUtils.clean(stack); 26 | const expected = utils.join([ 27 | 'mostInner (produce-long-stack-traces.js:10:5)', 28 | 'From previous event:', 29 | ' evenMoreInner (produce-long-stack-traces.js:9:29)', 30 | 'From previous event:', 31 | ' inner (produce-long-stack-traces.js:8:28)', 32 | 'From previous event:', 33 | ' outer (produce-long-stack-traces.js:7:27)', 34 | 'From previous event:', 35 | ' Object. (produce-long-stack-traces.js:6:36)' 36 | ]); 37 | 38 | t.equal(cleanedStack, expected); 39 | }); 40 | }); 41 | 42 | t.test('removes empty "From previous event:" sections from the bottom', t => { 43 | return longStackTraces.bluebird.bottom 44 | .then(stack => { 45 | const cleanedStack = stackUtils.clean(stack); 46 | 47 | const expected = utils.join([ 48 | 'mostInner (produce-long-stack-traces.js:43:6)', 49 | 'From previous event:', 50 | ' evenMoreInner (produce-long-stack-traces.js:42:30)', 51 | 'From previous event:', 52 | ' inner (produce-long-stack-traces.js:41:29)', 53 | 'From previous event:', 54 | ' outer (produce-long-stack-traces.js:40:28)' 55 | ]); 56 | 57 | t.equal(cleanedStack, expected); 58 | }); 59 | }); 60 | 61 | t.test('removes empty "From previous event:" sections from the top', t => { 62 | return longStackTraces.bluebird.top 63 | .then(stack => { 64 | const cleanedStack = stackUtils.clean(stack); 65 | 66 | const expected = utils.join([ 67 | 'From previous event:', 68 | ' evenMoreInner (produce-long-stack-traces.js:33:29)', 69 | 'From previous event:', 70 | ' inner (produce-long-stack-traces.js:32:28)', 71 | 'From previous event:', 72 | ' outer (produce-long-stack-traces.js:31:27)', 73 | 'From previous event:', 74 | ' Object. (produce-long-stack-traces.js:30:40)' 75 | ]); 76 | 77 | t.equal(cleanedStack, expected); 78 | }); 79 | }); 80 | 81 | t.test('removes empty "From previous event:" sections from the middle', t => { 82 | return longStackTraces.bluebird.middle 83 | .then(stack => { 84 | const cleanedStack = stackUtils.clean(stack); 85 | 86 | const expected = utils.join([ 87 | 'mostInner (produce-long-stack-traces.js:22:5)', 88 | 'From previous event:', 89 | ' evenMoreInner (produce-long-stack-traces.js:21:29)', 90 | 'From previous event:', 91 | ' inner (produce-long-stack-traces.js:20:10)', 92 | 'From previous event:', 93 | ' outer (produce-long-stack-traces.js:19:27)', 94 | 'From previous event:', 95 | ' Object. (produce-long-stack-traces.js:18:43)' 96 | ]); 97 | 98 | t.match(cleanedStack, expected); 99 | }); 100 | }); 101 | 102 | t.test('removes empty "Caused by:" sections from the top', t => { 103 | nestedErrors.top(stack => { 104 | const cleanedStack = stackUtils.clean(stack); 105 | 106 | const expected = utils.join([ 107 | 'Caused By: Error: baz', 108 | ' Object.module.exports.top (nested-errors.js:36:5)' 109 | ]); 110 | 111 | t.match(cleanedStack, expected); 112 | t.end(); 113 | }); 114 | }); 115 | 116 | t.test('removes empty "Caused by:" sections from the bottom', t => { 117 | nestedErrors.bottom(stack => { 118 | const cleanedStack = stackUtils.clean(stack); 119 | 120 | const expected = utils.join([ 121 | 'nested (nested-errors.js:9:6)', 122 | 'moreNested (nested-errors.js:15:3)', 123 | 'Caused By: BarError: bar: internal', 124 | ' moreNested (nested-errors.js:15:6)' 125 | ]); 126 | 127 | t.equal(cleanedStack, expected); 128 | t.end(); 129 | }); 130 | }); 131 | 132 | t.test('removes empty "Caused by:" sections from the middle', t => { 133 | nestedErrors.middle(stack => { 134 | const cleanedStack = stackUtils.clean(stack); 135 | 136 | const expected = utils.join([ 137 | 'nested-errors.js:41:6', 138 | 'Caused By: Error: bar', 139 | ' Object.module.exports.middle (nested-errors.js:42:5)' 140 | ]); 141 | 142 | t.match(cleanedStack, expected); 143 | t.end(); 144 | }); 145 | }); 146 | -------------------------------------------------------------------------------- /test/node-modules.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const t = require('tap'); 4 | 5 | const StackUtils = require('../'); 6 | 7 | function helper(fromModule, ignoredPackages) { 8 | const stackUtil = new StackUtils({ignoredPackages}); 9 | 10 | const result = stackUtil.clean('Error: Simulated\n' + 11 | ` at fn (/usr/src/stack-utils/node_modules/${fromModule}/index.js:1:1)\n`); 12 | if (ignoredPackages.includes(fromModule)) { 13 | t.notMatch(result, `node_modules/${fromModule}/`); 14 | } else { 15 | t.match(result, `node_modules/${fromModule}/`); 16 | } 17 | } 18 | 19 | const modules = ['arg', 'resolve-from', '@scoped/module']; 20 | for (const mod of modules) { 21 | helper(mod, []); 22 | helper(mod, [mod]); 23 | helper(mod, modules.filter(m => m !== mod)); 24 | helper(mod, modules); 25 | } 26 | -------------------------------------------------------------------------------- /test/parse-line-fixtures.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const t = require('tap'); 4 | const cases = require('./fixtures/parse-fixture.json'); 5 | const lines = Object.keys(cases); 6 | const StackUtils = require('../'); 7 | const stack = new StackUtils(); 8 | 9 | t.plan(lines.length * 2); 10 | lines.forEach(line => { 11 | const expect = cases[line]; 12 | t.match(stack.parseLine(line), expect, JSON.stringify(line)); 13 | line = line.replace(/^ {4}at /, ''); 14 | t.match(stack.parseLine(line), expect, JSON.stringify(line)); 15 | }); 16 | -------------------------------------------------------------------------------- /test/test.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | const path = require('path'); 4 | const t = require('tap'); 5 | const StackUtils = require('../'); 6 | const CaptureFixture = require('./fixtures/capture-fixture'); 7 | const utils = require('./_utils'); 8 | 9 | const LinuxStack1 = utils.join(linuxStack1(), internalStack()); 10 | const WindowsStack1 = utils.join(windowsStack1(), internalStack()); 11 | 12 | const version = process.version.slice(1).split('.').map(Number); 13 | 14 | t.test('must be called with new', t => { 15 | t.equal(typeof StackUtils, 'function'); 16 | const stackUtils = StackUtils; 17 | t.throws(() => stackUtils()); 18 | t.end(); 19 | }); 20 | 21 | t.test('ok if called without a process object', t => { 22 | const proc = process 23 | const cwd = proc.cwd().replace(/\\/g, '/') 24 | t.teardown(() => global.process = proc) 25 | global.process = null 26 | const StackUtils = t.mock('../', { 27 | // need a fresh copy of these because they also look at global process 28 | 'source-map-support': t.mock('source-map-support'), 29 | 'own-or-env': t.mock('own-or-env'), 30 | }) 31 | const expected = `${cwd}/foo (foo.js:3:8)\n` 32 | const raw = ` at ${expected}\n` 33 | const stack = new StackUtils({ internals: [] }) 34 | t.equal(stack.clean(raw), expected, 'no cwd is stripped off') 35 | t.end() 36 | }) 37 | 38 | t.test('clean: truncates cwd', t => { 39 | const expectedArray = [ 40 | 'foo (foo.js:3:8)', 41 | 'bar (foo.js:7:2)', 42 | 'bar (bar.js:4:2)', 43 | 'Object. (bar.js:7:1)', 44 | 'ontimeout (timers.js:365:14)', 45 | 'tryOnTimeout (timers.js:237:5)', 46 | 'Timer.listOnTimeout (timers.js:207:5)', 47 | '_combinedTickCallback (internal/process/next_tick.js:67:7)', 48 | 'process._tickCallback (internal/process/next_tick.js:98:9)', 49 | 'Module.runMain (module.js:645:11)', 50 | 'Module._compile (module.js:398:26)', 51 | 'Object.Module._extensions..js (module.js:405:10)', 52 | 'Module.load (module.js:344:32)', 53 | 'Function.Module._load (module.js:301:12)', 54 | 'Function.Module.runMain (module.js:430:10)', 55 | 'module.js:430:10', 56 | 'run (bootstrap_node.js:420:7)', 57 | 'startup (bootstrap_node.js:139:9)', 58 | 'bootstrap_node.js:535:3', 59 | 'startup (node.js:141:18)' 60 | ]; 61 | const expected = utils.join(expectedArray); 62 | 63 | let stack = new StackUtils({cwd: '/user/dev/project', internals: []}); 64 | t.equal(stack.clean(LinuxStack1), expected, 'accepts a linux string'); 65 | t.equal(stack.clean(LinuxStack1.split('\n')), expected, 'accepts an array'); 66 | t.equal(stack.clean(LinuxStack1.split('\n').slice(1)), expected, 'slices off the message line'); 67 | 68 | stack = new StackUtils({cwd: 'Z:\\user\\dev\\project', internals: []}); 69 | t.equal(stack.clean(WindowsStack1), expected, 'accepts a windows string'); 70 | 71 | const expectIndented = utils.join(expectedArray.map(a => ' ' + a)); 72 | t.equal(stack.clean(WindowsStack1, 4), expectIndented, 'indentation'); 73 | 74 | t.end(); 75 | }); 76 | 77 | t.test('clean: eliminates internals', t => { 78 | let stack = new StackUtils({cwd: '/user/dev'}); 79 | const expected = utils.join([ 80 | 'foo (project/foo.js:3:8)', 81 | 'bar (project/foo.js:7:2)', 82 | 'bar (project/bar.js:4:2)', 83 | 'Object. (project/bar.js:7:1)' 84 | ]); 85 | t.equal(stack.clean(LinuxStack1), expected); 86 | 87 | stack = new StackUtils({cwd: 'Z:\\user\\dev'}); 88 | t.equal(stack.clean(WindowsStack1), expected); 89 | t.end(); 90 | }); 91 | 92 | t.test('clean: returns null if it is all internals', t => { 93 | const stack = new StackUtils(); 94 | t.equal(stack.clean(utils.join(internalStack())), ''); 95 | t.end(); 96 | }); 97 | 98 | t.test('captureString: two redirects', t => { 99 | const stack = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 100 | const capture = new CaptureFixture(stack); 101 | 102 | const capturedString = capture.redirect1('redirect2', 'call', 'captureString'); 103 | t.equal(capturedString, utils.join([ 104 | 'CaptureFixture.call (capture-fixture.js:47:28)', 105 | 'CaptureFixture.redirect2 (capture-fixture.js:13:24)', 106 | 'CaptureFixture.redirect1 (capture-fixture.js:9:24)' 107 | ])); 108 | t.end(); 109 | }); 110 | 111 | t.test('captureString: with startStack function', t => { 112 | const stack = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 113 | const capture = new CaptureFixture(stack); 114 | 115 | const capturedString = capture.redirect1('redirect2', 'call', 'captureString', capture.call); 116 | t.equal(capturedString, utils.join([ 117 | 'CaptureFixture.redirect2 (capture-fixture.js:13:24)', 118 | 'CaptureFixture.redirect1 (capture-fixture.js:9:24)' 119 | ])); 120 | t.end(); 121 | }); 122 | 123 | t.test('captureString: with limit', t => { 124 | const stack = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 125 | const capture = new CaptureFixture(stack); 126 | 127 | const capturedString = capture.redirect1('redirect2', 'call', 'captureString', 1); 128 | t.equal(capturedString, utils.join([ 129 | 'CaptureFixture.call (capture-fixture.js:47:28)' 130 | ])); 131 | t.end(); 132 | }); 133 | 134 | t.test('captureString: with limit and startStack', t => { 135 | const stack = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 136 | const capture = new CaptureFixture(stack); 137 | 138 | const capturedString = capture.redirect1('redirect2', 'call', 'captureString', 1, capture.call); 139 | t.equal(capturedString, utils.join([ 140 | 'CaptureFixture.redirect2 (capture-fixture.js:13:24)' 141 | ])); 142 | t.end(); 143 | }); 144 | 145 | t.test('capture returns an array of call sites', t => { 146 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 147 | const capture = new CaptureFixture(stackUtil); 148 | const stack = capture.redirect1('call', 'capture').slice(0, 2); 149 | t.equal(stack[0].getFileName(), path.join(utils.fixtureDir, 'capture-fixture.js')); 150 | t.equal(stack[0].getFunctionName(), 'CaptureFixture.call'); 151 | t.equal(stack[1].getFunctionName(), 'redirect1'); 152 | t.end(); 153 | }); 154 | 155 | t.test('capture: with limit', t => { 156 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 157 | const capture = new CaptureFixture(stackUtil); 158 | const stack = capture.redirect1('redirect2', 'call', 'capture', 1); 159 | t.equal(stack.length, 1); 160 | t.equal(stack[0].getFunctionName(), 'CaptureFixture.call'); 161 | t.end(); 162 | }); 163 | 164 | t.test('capture: with stackStart function', t => { 165 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 166 | const capture = new CaptureFixture(stackUtil); 167 | const stack = capture.redirect1('redirect2', 'call', 'capture', capture.call); 168 | t.ok(stack.length > 1); 169 | t.equal(stack[0].getFunctionName(), 'redirect2'); 170 | t.end(); 171 | }); 172 | 173 | t.test('capture: with limit and stackStart function', t => { 174 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 175 | const capture = new CaptureFixture(stackUtil); 176 | const stack = capture.redirect1('redirect2', 'call', 'capture', 1, capture.call); 177 | t.equal(stack.length, 1); 178 | t.equal(stack[0].getFunctionName(), 'redirect2'); 179 | t.end(); 180 | }); 181 | 182 | t.test('capture: with wrapCallSite function', t => { 183 | const wrapper = callsite => ({ 184 | getMethodName () { 185 | return callsite.getMethodName(); 186 | }, 187 | getFunctionName () { 188 | return 'testOverrideFunctionName'; 189 | } 190 | }); 191 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir, wrapCallSite: wrapper}); 192 | const capture = new CaptureFixture(stackUtil); 193 | const stack = capture.redirect1('redirect2', 'call', 'capture', 1, capture.call); 194 | t.equal(stack.length, 1); 195 | t.equal(stack[0].getFunctionName(), 'testOverrideFunctionName'); 196 | t.equal(stack[0].getMethodName(), 'redirect2'); 197 | t.end(); 198 | }); 199 | 200 | t.test('at', t => { 201 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 202 | const capture = new CaptureFixture(stackUtil); 203 | const at = capture.redirect1('call', 'at'); 204 | 205 | t.same(at, { 206 | file: 'capture-fixture.js', 207 | line: 47, 208 | column: 28, 209 | type: 'CaptureFixture', 210 | function: 'CaptureFixture.call', 211 | method: 'call' 212 | }); 213 | t.end(); 214 | }); 215 | 216 | t.test('at: with stackStart', t => { 217 | const stackUtil = new StackUtils({internals: internals(), cwd: __dirname}); 218 | const capture = new CaptureFixture(stackUtil); 219 | 220 | const at = capture.redirect1('call', 'at', capture.call); 221 | 222 | t.same(at, { 223 | file: 'fixtures/capture-fixture.js', 224 | line: 9, 225 | column: 24, 226 | type: 'CaptureFixture', 227 | function: 'redirect1' 228 | }); 229 | t.end(); 230 | }); 231 | 232 | t.test('at: inside a constructor call', t => { 233 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 234 | const capture = new CaptureFixture(stackUtil); 235 | 236 | const at = capture.const('call', 'at', capture.call); 237 | 238 | // TODO: File an issue - if this assert fails, the power assert diagram renderer blows up. 239 | t.match(at, { 240 | file: 'capture-fixture.js', 241 | line: 20, 242 | column: 32, 243 | constructor: true, 244 | }); 245 | t.end(); 246 | }); 247 | 248 | t.test('at: method on an [Object] instance', t => { 249 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 250 | const capture = new CaptureFixture(stackUtil); 251 | 252 | const at = capture.const('obj', 'foo', 'call', 'at', capture.call); 253 | 254 | t.same(at, { 255 | file: 'capture-fixture.js', 256 | line: 29, 257 | column: 39, 258 | function: 'foo' 259 | }); 260 | t.end(); 261 | }); 262 | 263 | t.test('at: returns empty object if #capture() returns an empty stack', t => { 264 | const stackUtil = new StackUtils(); 265 | stackUtil.capture = () => []; 266 | t.same(stackUtil.at(), {}); 267 | t.end(); 268 | }); 269 | 270 | t.test('at: eval', t => { 271 | const stackUtil = new StackUtils({internals: internals(), cwd: utils.fixtureDir}); 272 | const capture = new CaptureFixture(stackUtil); 273 | 274 | const at = capture.eval('call', 'at', capture.call); 275 | const expected = { 276 | line: 1, 277 | column: 13, 278 | evalOrigin: /eval at eval \(.*capture-fixture.js:38:12\)/, 279 | function: 'eval' 280 | }; 281 | 282 | // TODO: There are some inconsistencies between this and how `parseLine` works. 283 | if (version[0] < 4) { 284 | expected.type = 'CaptureFixture'; 285 | expected.function = 'eval'; 286 | } 287 | 288 | t.match(at, expected); 289 | t.end(); 290 | }); 291 | 292 | t.test('parseLine', t => { 293 | const stack = new StackUtils({internals: internals(), cwd: '/user/dev/project'}); 294 | const capture = new CaptureFixture(stack); 295 | 296 | t.same(stack.parseLine('foo'), null, 'should not match'); 297 | 298 | t.same(stack.parseLine(' at bar (/user/dev/project/foo.js:3:8)'), { 299 | file: 'foo.js', 300 | line: 3, 301 | column: 8, 302 | function: 'bar' 303 | }); 304 | 305 | t.same(stack.parseLine(' at SomeClass.someFunc (/user/dev/project/foo.js:3:8)'), { 306 | file: 'foo.js', 307 | line: 3, 308 | column: 8, 309 | function: 'SomeClass.someFunc' 310 | }); 311 | 312 | // { "foo bar" () { throw new Error() } } 313 | t.same(stack.parseLine(' at Object.foo bar (/user/dev/project/foo.js:3:8)'), { 314 | file: 'foo.js', 315 | line: 3, 316 | column: 8, 317 | function: 'Object.foo bar' 318 | }); 319 | 320 | // Array.from({ *[Symbol.iterator] () { throw new Error() } }) 321 | t.same(stack.parseLine(' at Object.[Symbol.iterator] (/user/dev/project/foo.js:3:8)'), { 322 | file: 'foo.js', 323 | line: 3, 324 | column: 8, 325 | function: 'Object.[Symbol.iterator]' 326 | }); 327 | 328 | t.same(stack.parseLine(' at foo (/some/other/dir/file.js:3:8)'), { 329 | file: '/some/other/dir/file.js', 330 | line: 3, 331 | column: 8, 332 | function: 'foo' 333 | }); 334 | 335 | // TODO: report issue - this also causes power-assert diagram renderer to fail 336 | t.same(stack.parseLine(' at new Foo (/user/dev/project/foo.js:3:8)'), { 337 | file: 'foo.js', 338 | line: 3, 339 | column: 8, 340 | constructor: true, 341 | function: 'Foo' 342 | }); 343 | 344 | // EVAL 345 | const evalStack = capture.eval('error', 'foo').stack.split('\n'); 346 | 347 | const expected = { 348 | file: '', 349 | line: 1, 350 | column: 13, 351 | evalOrigin: 'eval', 352 | evalLine: 38, 353 | evalColumn: 12, 354 | evalFile: path.join(utils.fixtureDir, 'capture-fixture.js').replace(/\\/g, '/'), 355 | function: 'eval' 356 | }; 357 | 358 | if (version[0] < 4) { 359 | expected.function = 'CaptureFixture.eval'; 360 | } 361 | 362 | const actual = stack.parseLine(evalStack[2]); 363 | 364 | t.match(actual, expected); 365 | t.end(); 366 | }); 367 | 368 | t.test('parseLine: handles native errors', t => { 369 | const stackUtils = new StackUtils(); 370 | t.same(stackUtils.parseLine(' at Error (native)'), { 371 | native: true, 372 | function: 'Error' 373 | }); 374 | t.end(); 375 | }); 376 | 377 | t.test('parseLine: handles parens', t => { 378 | const line = ' at X. (/USER/Db (Person)/x/y.js:14:11)'; 379 | const stackUtils = new StackUtils(); 380 | t.same(stackUtils.parseLine(line), { 381 | line: 14, 382 | column: 11, 383 | file: '/USER/Db (Person)/x/y.js', 384 | function: 'X.' 385 | }); 386 | t.end(); 387 | }); 388 | 389 | function linuxStack1() { 390 | return [ 391 | 'Error: foo', 392 | ' at foo (/user/dev/project/foo.js:3:8)', 393 | ' at bar (/user/dev/project/foo.js:7:2)', 394 | ' at bar (/user/dev/project/bar.js:4:2)', 395 | ' at Object. (/user/dev/project/bar.js:7:1)' 396 | ]; 397 | } 398 | 399 | function windowsStack1() { 400 | return [ 401 | 'Error: foo', 402 | ' at foo (Z:\\user\\dev\\project\\foo.js:3:8)', 403 | ' at bar (Z:\\user\\dev\\project\\foo.js:7:2)', 404 | ' at bar (Z:\\user\\dev\\project\\bar.js:4:2)', 405 | ' at Object. (Z:\\user\\dev\\project\\bar.js:7:1)' 406 | ]; 407 | } 408 | 409 | function internalStack() { 410 | return [ 411 | ' at ontimeout (timers.js:365:14)', 412 | ' at tryOnTimeout (timers.js:237:5)', 413 | ' at Timer.listOnTimeout (timers.js:207:5)', 414 | ' at _combinedTickCallback (internal/process/next_tick.js:67:7)', 415 | ' at process._tickCallback (internal/process/next_tick.js:98:9)', 416 | ' at Module.runMain (module.js:645:11)', 417 | ' at Module._compile (module.js:398:26)', 418 | ' at Object.Module._extensions..js (module.js:405:10)', 419 | ' at Module.load (module.js:344:32)', 420 | ' at Function.Module._load (module.js:301:12)', 421 | ' at Function.Module.runMain (module.js:430:10)', 422 | ' at module.js:430:10', 423 | ' at run (bootstrap_node.js:420:7)', 424 | ' at startup (bootstrap_node.js:139:9)', 425 | ' at bootstrap_node.js:535:3', 426 | ' at startup (node.js:141:18)' 427 | ]; 428 | } 429 | 430 | function internals() { 431 | return StackUtils.nodeInternals().concat([ 432 | /test\.js:\d+:\d+\)?$/, 433 | /\/node_modules\// 434 | ]); 435 | } 436 | --------------------------------------------------------------------------------