├── .editorconfig ├── .eslintrc.yml ├── .github ├── CONTRIBUTING.md ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ └── ci.yml ├── .gitignore ├── .npmignore ├── CHANGELOG.md ├── LICENSE ├── README.md ├── bower.json ├── component.json ├── dist ├── error-stack-parser.js ├── error-stack-parser.min.js └── error-stack-parser.min.js.map ├── error-stack-parser.d.ts ├── error-stack-parser.js ├── jsdoc.conf.json ├── karma.conf.ci.js ├── karma.conf.js ├── package-lock.json ├── package.json └── spec ├── error-stack-parser-spec.js ├── fixtures └── captured-errors.js └── spec-helper.js /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | end_of_line = lf 9 | charset = utf-8 10 | trim_trailing_whitespace = true 11 | insert_final_newline = true 12 | 13 | [*.{js,json}] 14 | indent_style = space 15 | indent_size = 4 16 | 17 | [package.json] 18 | indent_size = 2 19 | 20 | [.{jshintrc,jscsrc}] 21 | indent_style = space 22 | indent_size = 4 23 | 24 | [*.{yml,yaml}] 25 | indent_style = space 26 | indent_size = 2 27 | 28 | [*.md] 29 | trim_trailing_whitespace = false 30 | -------------------------------------------------------------------------------- /.eslintrc.yml: -------------------------------------------------------------------------------- 1 | env: 2 | browser: true 3 | commonjs: true 4 | node: true 5 | extends: 'eslint:recommended' 6 | globals: 7 | beforeEach: false 8 | define: false 9 | describe: false 10 | expect: false 11 | it: false 12 | jasmine: false 13 | parserOptions: 14 | ecmaVersion: 5 15 | ignorePatterns: 16 | - 'coverage/**' 17 | - 'dist/**' 18 | - '**/*.ts' 19 | rules: 20 | indent: 21 | - error 22 | - 4 23 | linebreak-style: 24 | - error 25 | - unix 26 | quotes: 27 | - error 28 | - single 29 | semi: 30 | - error 31 | - always 32 | no-console: 33 | - error 34 | - allow: 35 | - error 36 | -------------------------------------------------------------------------------- /.github/CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | ## Submitting bugs 2 | Please include the following information to help us reproduce and fix: 3 | 4 | * What you did 5 | * What you expected to happen 6 | * What actually happened 7 | * Browser and version 8 | * Example code that reproduces the problem (if possible) 9 | * *(l33t mode)* A failing test 10 | 11 | ## Making contributions 12 | Want to be listed as a *Contributor*? Make a pull request with: 13 | 14 | * Unit and/or functional tests that validate changes you're making. 15 | * Run unit tests in the latest IE, Firefox, Chrome, Safari and Opera and make sure they pass. 16 | * Rebase your changes onto origin/HEAD if you can do so cleanly. 17 | * If submitting additional functionality, provide an example of how to use it. 18 | * Please keep code style consistent with surrounding code. 19 | 20 | ## Dev Setup 21 | * Make sure you have [NodeJS v16.x](https://nodejs.org/) installed 22 | * Run `npm ci` from the project directory 23 | 24 | ## Linting 25 | * Run `npm run lint` to run ESLint 26 | 27 | ## Testing 28 | * (Local) Run `npm test`. Make sure [Karma Local Config](karma.conf.js) has the browsers you want 29 | * (Any browser, remotely) If you have a [Sauce Labs](https://saucelabs.com) account, you can run `npm run test-ci` 30 | Make sure the target browser is enabled in [Karma CI Config](karma.conf.ci.js) 31 | Otherwise, GitHub Actions will run all browsers if you submit a Pull Request. 32 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: a:bug 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | 16 | ## Current Behavior 17 | 18 | 19 | 20 | ## Steps to Reproduce (for bugs) 21 | 22 | 23 | 24 | ## Context 25 | 26 | 27 | 28 | ## Your Environment 29 | 30 | * Package version: 31 | * Browser name and version: 32 | * OS version (desktop or mobile): 33 | * Link to your project: 34 | 35 | ## Possible Solution 36 | 37 | 38 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: a:feature 6 | assignees: '' 7 | 8 | --- 9 | 10 | 11 | 12 | ## Expected Behavior 13 | 14 | 15 | 16 | ## Context 17 | 18 | 19 | 20 | ## Possible Solution 21 | 22 | 23 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | ## Description 4 | 5 | 6 | ## Motivation and Context 7 | 8 | 9 | 10 | ## How Has This Been Tested? 11 | 12 | 13 | 14 | 15 | ## Types of changes 16 | 17 | - [ ] Bug fix (non-breaking change which fixes an issue) 18 | - [ ] New feature (non-breaking change which adds functionality) 19 | - [ ] Breaking change (fix or feature that would cause existing functionality to change) 20 | 21 | ## Checklist: 22 | 23 | 24 | - [ ] `npm run lint` passes without errors 25 | - [ ] `npm run test` passes without errors 26 | - [ ] I have read the [contribution guidelines](CONTRIBUTING.md) 27 | - [ ] I have updated the documentation accordingly 28 | - [ ] I have added tests to cover my changes 29 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: Continuous Integration 2 | on: [push] 3 | 4 | jobs: 5 | tests: 6 | runs-on: ubuntu-latest 7 | environment: sauce 8 | permissions: read-all 9 | env: 10 | SAUCE_USERNAME: ${{ secrets.SAUCE_USERNAME }} 11 | SAUCE_ACCESS_KEY: ${{ secrets.SAUCE_ACCESS_KEY }} 12 | SAUCE_TUNNEL_ID: github-action-tunnel 13 | steps: 14 | - name: Setup sauce connect 15 | uses: saucelabs/sauce-connect-action@v2 16 | with: 17 | username: ${{ secrets.SAUCE_USERNAME }} 18 | accessKey: ${{ secrets.SAUCE_ACCESS_KEY }} 19 | tunnelIdentifier: github-action-tunnel 20 | scVersion: 4.7.1 21 | - name: Checkout 22 | uses: actions/checkout@v2 23 | - uses: actions/setup-node@v2 24 | with: 25 | node-version: 16 26 | - name: Cache dependencies 27 | uses: actions/cache@v2 28 | with: 29 | path: ~/.npm 30 | key: npm-${{ hashFiles('package-lock.json') }} 31 | restore-keys: npm- 32 | - name: Install packages 33 | run: npm ci 34 | - name: Lint code 35 | run: npm run lint 36 | - name: Run browser tests in Saucelabs 37 | run: npm run test-ci 38 | timeout-minutes: 5 39 | - name: Install lcov 40 | run: | 41 | sudo apt update 42 | sudo apt install lcov 43 | - name: Merge lcov reports 44 | run: find coverage -name lcov.info -exec echo -a \"{}\" \; | xargs lcov -o coverage/lcov.info 45 | - name: Coveralls 46 | uses: coverallsapp/github-action@master 47 | with: 48 | github-token: ${{ secrets.github_token }} 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.seed 2 | *.log 3 | *.out 4 | *.pid 5 | *.gz 6 | 7 | bower_components 8 | node_modules 9 | build 10 | coverage 11 | 12 | .idea 13 | .coveralls.yml 14 | dist 15 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | build/ 3 | coverage/ 4 | spec/ 5 | *.log 6 | .coveralls.yml 7 | .editorconfig 8 | .idea 9 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ## v2.0.0 2 | * Update stackframe dependency to v1.x. Stackframes are constructed and accessed differently. 3 | See the [stackframe CHANGELOG](https://github.com/stacktracejs/stackframe/blob/master/CHANGELOG.md#v10x) for details. 4 | 5 | ## v1.3.6 6 | * Handle stack frames with no line/column information 7 | 8 | ## v1.3.4 9 | * Avoid file names 10 | 11 | ## v1.3.2 12 | * Handle Safari stack entries with no location information 13 | 14 | ## v1.3.0 15 | * Significantly improved handling of eval() 16 | * Add many browsers to CI 17 | 18 | ## v1.2.2 19 | * Handle native functions in V8 stack traces 20 | 21 | ## v1.2.0 22 | * Propagate "(native)" locations instead of defaulting to `undefined` 23 | 24 | ## v1.1.1 25 | * Make sure to include direct dependencies in distributed JS files 26 | 27 | ## v1.1.0 28 | * Move polyfills to separate, optional file 29 | * Better docs 30 | 31 | ## v1.0.0 32 | * Fully tested on old IEs, production-ready 33 | 34 | ## v0.2.4 35 | * Fix moar boogs with Opera impl 36 | 37 | ## v0.2.3 38 | * Fix boogs with Opera impl 39 | 40 | ## v0.2.2 41 | * Name functions such that they can can be filtered out by stacktrace.js 42 | 43 | ## v0.2.1 44 | * Provide standard distribution (minified and unminified). 45 | * Slimmer node package 46 | 47 | ## v0.2.0 48 | * Remove constructor 49 | * Fix boogs 50 | 51 | ## v0.1.0 52 | * Re-write from stacktrace.js 53 | 54 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2017 Eric Wendelin and other contributors 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy of 4 | this software and associated documentation files (the "Software"), to deal in 5 | the Software without restriction, including without limitation the rights to 6 | use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies 7 | of the Software, and to permit persons to whom the Software is furnished to do 8 | so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in all 11 | copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 19 | SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | error-stack-parser.js - Extract meaning from JS Errors 2 | =============== 3 | [![Build Status](https://img.shields.io/github/workflow/status/stacktracejs/error-stack-parser/Continuous%20Integration/master?logo=github&style=flat-square)](https://github.com/stacktracejs/error-stack-parser/actions?query=workflow%3AContinuous+Integration+branch%3Amaster) 4 | [![Coverage Status](https://img.shields.io/coveralls/stacktracejs/error-stack-parser.svg?style=flat-square)](https://coveralls.io/r/stacktracejs/error-stack-parser?branch=master) 5 | [![GitHub license](https://img.shields.io/github/license/stacktracejs/error-stack-parser.svg?style=flat-square)](https://opensource.org/licenses/MIT) 6 | [![size with dependencies](https://img.shields.io/badge/size-4.8k-green.svg?style=flat-square)](https://github.com/stacktracejs/error-stack-parser/releases) 7 | [![gzip size](https://img.shields.io/badge/gzipped-1.8k-green.svg?style=flat-square)](https://github.com/stacktracejs/error-stack-parser/releases) 8 | [![module format](https://img.shields.io/badge/module%20format-umd-lightgrey.svg?style=flat-square&colorB=ff69b4)](https://github.com/stacktracejs/error-stack-parser/releases) 9 | [![code of conduct](https://img.shields.io/badge/code%20of-conduct-lightgrey.svg?style=flat-square&colorB=ff69b4)](http://todogroup.org/opencodeofconduct/#stacktrace.js/me@eriwen.com) 10 | [![jsDelivr Hits](https://data.jsdelivr.com/v1/package/npm/error-stack-parser/badge)](https://www.jsdelivr.com/package/npm/error-stack-parser) 11 | 12 | Simple, cross-browser [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) parser. 13 | This library parses and extracts function names, URLs, line numbers, and column numbers from the given Error's `stack` as 14 | an Array of [StackFrame](http://git.io/stackframe)s. 15 | 16 | Once you have parsed out StackFrames, you can do much more interesting things. See [stacktrace-gps](http://git.io/stacktrace-gps). 17 | 18 | Note that in IE9 and earlier, `Error` objects don't have enough information to extract much of anything. In IE 10, `Error`s 19 | are given a `stack` once they're `throw`n. 20 | 21 | ## Browser Support 22 | [![Sauce Test Status](https://saucelabs.com/browser-matrix/stacktracejs.svg)](https://saucelabs.com/u/stacktracejs) 23 | 24 | ## Usage 25 | ```js 26 | ErrorStackParser.parse(new Error('BOOM')); 27 | 28 | => [ 29 | StackFrame({functionName: 'foo', args: [], fileName: 'path/to/file.js', lineNumber: 35, columnNumber: 79, isNative: false, isEval: false}), 30 | StackFrame({functionName: 'Bar', fileName: 'https://cdn.somewherefast.com/utils.min.js', lineNumber: 1, columnNumber: 832, isNative: false, isEval: false, isConstructor: true}), 31 | StackFrame(... and so on ...) 32 | ] 33 | ``` 34 | 35 | ## Installation 36 | ```bash 37 | npm install error-stack-parser 38 | bower install error-stack-parser 39 | https://raw.githubusercontent.com/stacktracejs/error-stack-parser/master/dist/error-stack-parser.min.js 40 | ``` 41 | 42 | ## Contributing 43 | Want to be listed as a *Contributor*? Start with the [Contributing Guide](.github/CONTRIBUTING.md)! 44 | 45 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "error-stack-parser", 3 | "main": "./dist/error-stack-parser.js", 4 | "homepage": "https://github.com/stacktracejs/error-stack-parser", 5 | "authors": [ 6 | "Eric Wendelin (https://www.eriwen.com)", 7 | "Victor Homyakov (https://github.com/victor-homyakov)" 8 | ], 9 | "description": "Extract meaning from JS Errors", 10 | "dependencies": { 11 | "stackframe": "^1.0.2" 12 | }, 13 | "moduleType": [ 14 | "amd", 15 | "globals", 16 | "node" 17 | ], 18 | "keywords": [ 19 | "stacktrace", 20 | "stack-trace", 21 | "backtrace", 22 | "cross-browser", 23 | "framework-agnostic", 24 | "error", 25 | "debugger" 26 | ], 27 | "license": "MIT", 28 | "ignore": [ 29 | "**/.*", 30 | "node_modules", 31 | "spec", 32 | "coverage" 33 | ] 34 | } 35 | -------------------------------------------------------------------------------- /component.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "error-stack-parser", 3 | "repository": "stacktracejs/error-stack-parser", 4 | "description": "Extract meaning from JS Errors", 5 | "version": "2.0.0", 6 | "keywords": [ 7 | "stacktrace", 8 | "error", 9 | "stack" 10 | ], 11 | "main": "error-stack-parser.js", 12 | "scripts": [ 13 | "error-stack-parser.js" 14 | ], 15 | "dependencies": { 16 | "stacktracejs/stackframe": "1.0.2" 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /dist/error-stack-parser.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | 'use strict'; 3 | // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers. 4 | 5 | /* istanbul ignore next */ 6 | if (typeof define === 'function' && define.amd) { 7 | define('error-stack-parser', ['stackframe'], factory); 8 | } else if (typeof exports === 'object') { 9 | module.exports = factory(require('stackframe')); 10 | } else { 11 | root.ErrorStackParser = factory(root.StackFrame); 12 | } 13 | }(this, function ErrorStackParser(StackFrame) { 14 | 'use strict'; 15 | 16 | var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/; 17 | var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; 18 | var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/; 19 | 20 | return { 21 | /** 22 | * Given an Error object, extract the most information from it. 23 | * 24 | * @param {Error} error object 25 | * @return {Array} of StackFrames 26 | */ 27 | parse: function ErrorStackParser$$parse(error) { 28 | if (typeof error.stacktrace !== 'undefined' || typeof error['opera#sourceloc'] !== 'undefined') { 29 | return this.parseOpera(error); 30 | } else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) { 31 | return this.parseV8OrIE(error); 32 | } else if (error.stack) { 33 | return this.parseFFOrSafari(error); 34 | } else { 35 | throw new Error('Cannot parse given Error object'); 36 | } 37 | }, 38 | 39 | // Separate line and column numbers from a string of the form: (URI:Line:Column) 40 | extractLocation: function ErrorStackParser$$extractLocation(urlLike) { 41 | // Fail-fast but return locations like "(native)" 42 | if (urlLike.indexOf(':') === -1) { 43 | return [urlLike]; 44 | } 45 | 46 | var regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/; 47 | var parts = regExp.exec(urlLike.replace(/[()]/g, '')); 48 | return [parts[1], parts[2] || undefined, parts[3] || undefined]; 49 | }, 50 | 51 | parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) { 52 | var filtered = error.stack.split('\n').filter(function(line) { 53 | return !!line.match(CHROME_IE_STACK_REGEXP); 54 | }, this); 55 | 56 | return filtered.map(function(line) { 57 | if (line.indexOf('(eval ') > -1) { 58 | // Throw away eval information until we implement stacktrace.js/stackframe#8 59 | line = line.replace(/eval code/g, 'eval').replace(/(\(eval at [^()]*)|(,.*$)/g, ''); 60 | } 61 | var sanitizedLine = line.replace(/^\s+/, '').replace(/\(eval code/g, '(').replace(/^.*?\s+/, ''); 62 | 63 | // capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in 64 | // case it has spaces in it, as the string is split on \s+ later on 65 | var location = sanitizedLine.match(/ (\(.+\)$)/); 66 | 67 | // remove the parenthesized location from the line, if it was matched 68 | sanitizedLine = location ? sanitizedLine.replace(location[0], '') : sanitizedLine; 69 | 70 | // if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine 71 | // because this line doesn't have function name 72 | var locationParts = this.extractLocation(location ? location[1] : sanitizedLine); 73 | var functionName = location && sanitizedLine || undefined; 74 | var fileName = ['eval', ''].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0]; 75 | 76 | return new StackFrame({ 77 | functionName: functionName, 78 | fileName: fileName, 79 | lineNumber: locationParts[1], 80 | columnNumber: locationParts[2], 81 | source: line 82 | }); 83 | }, this); 84 | }, 85 | 86 | parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) { 87 | var filtered = error.stack.split('\n').filter(function(line) { 88 | return !line.match(SAFARI_NATIVE_CODE_REGEXP); 89 | }, this); 90 | 91 | return filtered.map(function(line) { 92 | // Throw away eval information until we implement stacktrace.js/stackframe#8 93 | if (line.indexOf(' > eval') > -1) { 94 | line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ':$1'); 95 | } 96 | 97 | if (line.indexOf('@') === -1 && line.indexOf(':') === -1) { 98 | // Safari eval frames only have function names and nothing else 99 | return new StackFrame({ 100 | functionName: line 101 | }); 102 | } else { 103 | var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/; 104 | var matches = line.match(functionNameRegex); 105 | var functionName = matches && matches[1] ? matches[1] : undefined; 106 | var locationParts = this.extractLocation(line.replace(functionNameRegex, '')); 107 | 108 | return new StackFrame({ 109 | functionName: functionName, 110 | fileName: locationParts[0], 111 | lineNumber: locationParts[1], 112 | columnNumber: locationParts[2], 113 | source: line 114 | }); 115 | } 116 | }, this); 117 | }, 118 | 119 | parseOpera: function ErrorStackParser$$parseOpera(e) { 120 | if (!e.stacktrace || (e.message.indexOf('\n') > -1 && 121 | e.message.split('\n').length > e.stacktrace.split('\n').length)) { 122 | return this.parseOpera9(e); 123 | } else if (!e.stack) { 124 | return this.parseOpera10(e); 125 | } else { 126 | return this.parseOpera11(e); 127 | } 128 | }, 129 | 130 | parseOpera9: function ErrorStackParser$$parseOpera9(e) { 131 | var lineRE = /Line (\d+).*script (?:in )?(\S+)/i; 132 | var lines = e.message.split('\n'); 133 | var result = []; 134 | 135 | for (var i = 2, len = lines.length; i < len; i += 2) { 136 | var match = lineRE.exec(lines[i]); 137 | if (match) { 138 | result.push(new StackFrame({ 139 | fileName: match[2], 140 | lineNumber: match[1], 141 | source: lines[i] 142 | })); 143 | } 144 | } 145 | 146 | return result; 147 | }, 148 | 149 | parseOpera10: function ErrorStackParser$$parseOpera10(e) { 150 | var lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; 151 | var lines = e.stacktrace.split('\n'); 152 | var result = []; 153 | 154 | for (var i = 0, len = lines.length; i < len; i += 2) { 155 | var match = lineRE.exec(lines[i]); 156 | if (match) { 157 | result.push( 158 | new StackFrame({ 159 | functionName: match[3] || undefined, 160 | fileName: match[2], 161 | lineNumber: match[1], 162 | source: lines[i] 163 | }) 164 | ); 165 | } 166 | } 167 | 168 | return result; 169 | }, 170 | 171 | // Opera 10.65+ Error.stack very similar to FF/Safari 172 | parseOpera11: function ErrorStackParser$$parseOpera11(error) { 173 | var filtered = error.stack.split('\n').filter(function(line) { 174 | return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/); 175 | }, this); 176 | 177 | return filtered.map(function(line) { 178 | var tokens = line.split('@'); 179 | var locationParts = this.extractLocation(tokens.pop()); 180 | var functionCall = (tokens.shift() || ''); 181 | var functionName = functionCall 182 | .replace(//, '$2') 183 | .replace(/\([^)]*\)/g, '') || undefined; 184 | var argsRaw; 185 | if (functionCall.match(/\(([^)]*)\)/)) { 186 | argsRaw = functionCall.replace(/^[^(]+\(([^)]*)\)$/, '$1'); 187 | } 188 | var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ? 189 | undefined : argsRaw.split(','); 190 | 191 | return new StackFrame({ 192 | functionName: functionName, 193 | args: args, 194 | fileName: locationParts[0], 195 | lineNumber: locationParts[1], 196 | columnNumber: locationParts[2], 197 | source: line 198 | }); 199 | }, this); 200 | } 201 | }; 202 | })); 203 | -------------------------------------------------------------------------------- /dist/error-stack-parser.min.js: -------------------------------------------------------------------------------- 1 | !function(e,t){"use strict";"function"==typeof define&&define.amd?define("stackframe",[],t):"object"==typeof exports?module.exports=t():e.StackFrame=t()}(this,function(){"use strict";function e(e){return e.charAt(0).toUpperCase()+e.substring(1)}function t(e){return function(){return this[e]}}var r=["isConstructor","isEval","isNative","isToplevel"],n=["columnNumber","lineNumber"],i=["fileName","functionName","source"],a=r.concat(n,i,["args"],["evalOrigin"]);function o(t){if(t)for(var r=0;r-1&&(t=t.replace(/eval code/g,"eval").replace(/(\(eval at [^()]*)|(,.*$)/g,""));var r=t.replace(/^\s+/,"").replace(/\(eval code/g,"(").replace(/^.*?\s+/,""),n=r.match(/ (\(.+\)$)/);r=n?r.replace(n[0],""):r;var i=this.extractLocation(n?n[1]:r),a=n&&r||void 0,o=["eval",""].indexOf(i[0])>-1?void 0:i[0];return new e({functionName:a,fileName:o,lineNumber:i[1],columnNumber:i[2],source:t})},this)},parseFFOrSafari:function(t){return t.stack.split("\n").filter(function(e){return!e.match(n)},this).map(function(t){if(t.indexOf(" > eval")>-1&&(t=t.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g,":$1")),-1===t.indexOf("@")&&-1===t.indexOf(":"))return new e({functionName:t});var r=/((.*".+"[^@]*)?[^@]*)(?:@)/,n=t.match(r),i=n&&n[1]?n[1]:void 0,a=this.extractLocation(t.replace(r,""));return new e({functionName:i,fileName:a[0],lineNumber:a[1],columnNumber:a[2],source:t})},this)},parseOpera:function(e){return!e.stacktrace||e.message.indexOf("\n")>-1&&e.message.split("\n").length>e.stacktrace.split("\n").length?this.parseOpera9(e):e.stack?this.parseOpera11(e):this.parseOpera10(e)},parseOpera9:function(t){for(var r=/Line (\d+).*script (?:in )?(\S+)/i,n=t.message.split("\n"),i=[],a=2,o=n.length;a/,"$2").replace(/\([^)]*\)/g,"")||void 0;a.match(/\(([^)]*)\)/)&&(r=a.replace(/^[^(]+\(([^)]*)\)$/,"$1"));var s=void 0===r||"[arguments not available]"===r?void 0:r.split(",");return new e({functionName:o,args:s,fileName:i[0],lineNumber:i[1],columnNumber:i[2],source:t})},this)}}}); 2 | //# sourceMappingURL=error-stack-parser.min.js.map -------------------------------------------------------------------------------- /dist/error-stack-parser.min.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"sources":["node_modules/stackframe/stackframe.js","error-stack-parser.js"],"names":["root","factory","define","amd","exports","module","StackFrame","this","_capitalize","str","charAt","toUpperCase","substring","_getter","p","booleanProps","numericProps","stringProps","props","concat","obj","i","length","undefined","prototype","getArgs","args","setArgs","v","Object","toString","call","TypeError","getEvalOrigin","evalOrigin","setEvalOrigin","fileName","getFileName","lineNumber","getLineNumber","columnNumber","getColumnNumber","functionName","getFunctionName","getIsEval","fromString","argsStartIndex","indexOf","argsEndIndex","lastIndexOf","split","locationString","parts","exec","Boolean","j","n","isNaN","parseFloat","isFinite","Number","k","String","require","ErrorStackParser","FIREFOX_SAFARI_STACK_REGEXP","CHROME_IE_STACK_REGEXP","SAFARI_NATIVE_CODE_REGEXP","parse","error","stacktrace","parseOpera","stack","match","parseV8OrIE","parseFFOrSafari","Error","extractLocation","urlLike","replace","filter","line","map","sanitizedLine","location","locationParts","source","functionNameRegex","matches","e","message","parseOpera9","parseOpera11","parseOpera10","lineRE","lines","result","len","push","argsRaw","tokens","pop","functionCall","shift"],"mappings":"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"} -------------------------------------------------------------------------------- /error-stack-parser.d.ts: -------------------------------------------------------------------------------- 1 | // Type definitions for ErrorStackParser v2.1.0 2 | // Project: https://github.com/stacktracejs/error-stack-parser 3 | // Definitions by: Eric Wendelin 4 | // Definitions: https://github.com/DefinitelyTyped/DefinitelyTyped 5 | 6 | import StackFrame = require("stackframe"); 7 | 8 | declare namespace ErrorStackParser { 9 | export type {StackFrame}; 10 | /** 11 | * Given an Error object, extract the most information from it. 12 | * 13 | * @param {Error} error object 14 | * @return {Array} of StackFrames 15 | */ 16 | export function parse(error: Error): StackFrame[]; 17 | } 18 | 19 | export = ErrorStackParser; 20 | -------------------------------------------------------------------------------- /error-stack-parser.js: -------------------------------------------------------------------------------- 1 | (function(root, factory) { 2 | 'use strict'; 3 | // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers. 4 | 5 | /* istanbul ignore next */ 6 | if (typeof define === 'function' && define.amd) { 7 | define('error-stack-parser', ['stackframe'], factory); 8 | } else if (typeof exports === 'object') { 9 | module.exports = factory(require('stackframe')); 10 | } else { 11 | root.ErrorStackParser = factory(root.StackFrame); 12 | } 13 | }(this, function ErrorStackParser(StackFrame) { 14 | 'use strict'; 15 | 16 | var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+:\d+/; 17 | var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+:\d+|\(native\))/m; 18 | var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code])?$/; 19 | 20 | return { 21 | /** 22 | * Given an Error object, extract the most information from it. 23 | * 24 | * @param {Error} error object 25 | * @return {Array} of StackFrames 26 | */ 27 | parse: function ErrorStackParser$$parse(error) { 28 | if (typeof error.stacktrace !== 'undefined' || typeof error['opera#sourceloc'] !== 'undefined') { 29 | return this.parseOpera(error); 30 | } else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) { 31 | return this.parseV8OrIE(error); 32 | } else if (error.stack) { 33 | return this.parseFFOrSafari(error); 34 | } else { 35 | throw new Error('Cannot parse given Error object'); 36 | } 37 | }, 38 | 39 | // Separate line and column numbers from a string of the form: (URI:Line:Column) 40 | extractLocation: function ErrorStackParser$$extractLocation(urlLike) { 41 | // Fail-fast but return locations like "(native)" 42 | if (urlLike.indexOf(':') === -1) { 43 | return [urlLike]; 44 | } 45 | 46 | var regExp = /(.+?)(?::(\d+))?(?::(\d+))?$/; 47 | var parts = regExp.exec(urlLike.replace(/[()]/g, '')); 48 | return [parts[1], parts[2] || undefined, parts[3] || undefined]; 49 | }, 50 | 51 | parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) { 52 | var filtered = error.stack.split('\n').filter(function(line) { 53 | return !!line.match(CHROME_IE_STACK_REGEXP); 54 | }, this); 55 | 56 | return filtered.map(function(line) { 57 | if (line.indexOf('(eval ') > -1) { 58 | // Throw away eval information until we implement stacktrace.js/stackframe#8 59 | line = line.replace(/eval code/g, 'eval').replace(/(\(eval at [^()]*)|(,.*$)/g, ''); 60 | } 61 | var sanitizedLine = line.replace(/^\s+/, '').replace(/\(eval code/g, '(').replace(/^.*?\s+/, ''); 62 | 63 | // capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in 64 | // case it has spaces in it, as the string is split on \s+ later on 65 | var location = sanitizedLine.match(/ (\(.+\)$)/); 66 | 67 | // remove the parenthesized location from the line, if it was matched 68 | sanitizedLine = location ? sanitizedLine.replace(location[0], '') : sanitizedLine; 69 | 70 | // if a location was matched, pass it to extractLocation() otherwise pass all sanitizedLine 71 | // because this line doesn't have function name 72 | var locationParts = this.extractLocation(location ? location[1] : sanitizedLine); 73 | var functionName = location && sanitizedLine || undefined; 74 | var fileName = ['eval', ''].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0]; 75 | 76 | return new StackFrame({ 77 | functionName: functionName, 78 | fileName: fileName, 79 | lineNumber: locationParts[1], 80 | columnNumber: locationParts[2], 81 | source: line 82 | }); 83 | }, this); 84 | }, 85 | 86 | parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) { 87 | var filtered = error.stack.split('\n').filter(function(line) { 88 | return !line.match(SAFARI_NATIVE_CODE_REGEXP); 89 | }, this); 90 | 91 | return filtered.map(function(line) { 92 | // Throw away eval information until we implement stacktrace.js/stackframe#8 93 | if (line.indexOf(' > eval') > -1) { 94 | line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval:\d+:\d+/g, ':$1'); 95 | } 96 | 97 | if (line.indexOf('@') === -1 && line.indexOf(':') === -1) { 98 | // Safari eval frames only have function names and nothing else 99 | return new StackFrame({ 100 | functionName: line 101 | }); 102 | } else { 103 | var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/; 104 | var matches = line.match(functionNameRegex); 105 | var functionName = matches && matches[1] ? matches[1] : undefined; 106 | var locationParts = this.extractLocation(line.replace(functionNameRegex, '')); 107 | 108 | return new StackFrame({ 109 | functionName: functionName, 110 | fileName: locationParts[0], 111 | lineNumber: locationParts[1], 112 | columnNumber: locationParts[2], 113 | source: line 114 | }); 115 | } 116 | }, this); 117 | }, 118 | 119 | parseOpera: function ErrorStackParser$$parseOpera(e) { 120 | if (!e.stacktrace || (e.message.indexOf('\n') > -1 && 121 | e.message.split('\n').length > e.stacktrace.split('\n').length)) { 122 | return this.parseOpera9(e); 123 | } else if (!e.stack) { 124 | return this.parseOpera10(e); 125 | } else { 126 | return this.parseOpera11(e); 127 | } 128 | }, 129 | 130 | parseOpera9: function ErrorStackParser$$parseOpera9(e) { 131 | var lineRE = /Line (\d+).*script (?:in )?(\S+)/i; 132 | var lines = e.message.split('\n'); 133 | var result = []; 134 | 135 | for (var i = 2, len = lines.length; i < len; i += 2) { 136 | var match = lineRE.exec(lines[i]); 137 | if (match) { 138 | result.push(new StackFrame({ 139 | fileName: match[2], 140 | lineNumber: match[1], 141 | source: lines[i] 142 | })); 143 | } 144 | } 145 | 146 | return result; 147 | }, 148 | 149 | parseOpera10: function ErrorStackParser$$parseOpera10(e) { 150 | var lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i; 151 | var lines = e.stacktrace.split('\n'); 152 | var result = []; 153 | 154 | for (var i = 0, len = lines.length; i < len; i += 2) { 155 | var match = lineRE.exec(lines[i]); 156 | if (match) { 157 | result.push( 158 | new StackFrame({ 159 | functionName: match[3] || undefined, 160 | fileName: match[2], 161 | lineNumber: match[1], 162 | source: lines[i] 163 | }) 164 | ); 165 | } 166 | } 167 | 168 | return result; 169 | }, 170 | 171 | // Opera 10.65+ Error.stack very similar to FF/Safari 172 | parseOpera11: function ErrorStackParser$$parseOpera11(error) { 173 | var filtered = error.stack.split('\n').filter(function(line) { 174 | return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/); 175 | }, this); 176 | 177 | return filtered.map(function(line) { 178 | var tokens = line.split('@'); 179 | var locationParts = this.extractLocation(tokens.pop()); 180 | var functionCall = (tokens.shift() || ''); 181 | var functionName = functionCall 182 | .replace(//, '$2') 183 | .replace(/\([^)]*\)/g, '') || undefined; 184 | var argsRaw; 185 | if (functionCall.match(/\(([^)]*)\)/)) { 186 | argsRaw = functionCall.replace(/^[^(]+\(([^)]*)\)$/, '$1'); 187 | } 188 | var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ? 189 | undefined : argsRaw.split(','); 190 | 191 | return new StackFrame({ 192 | functionName: functionName, 193 | args: args, 194 | fileName: locationParts[0], 195 | lineNumber: locationParts[1], 196 | columnNumber: locationParts[2], 197 | source: line 198 | }); 199 | }, this); 200 | } 201 | }; 202 | })); 203 | -------------------------------------------------------------------------------- /jsdoc.conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "docset" : { 3 | "name": "ErrorStackParser", 4 | "icon": "../logos/StackTraceLogo16x16.png", 5 | "enableJavascript": true 6 | }, 7 | "opts": { 8 | "template": "node_modules/jsdoc-dash-template", 9 | "destination": "dist" 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /karma.conf.ci.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | if (!process.env.SAUCE_USERNAME || !process.env.SAUCE_ACCESS_KEY) { 3 | console.error('Make sure the SAUCE_USERNAME and SAUCE_ACCESS_KEY environment variables are set.'); 4 | process.exit(1); 5 | } 6 | 7 | // Check out https://saucelabs.com/platforms for all browser/platform combos 8 | var customLaunchers = { 9 | slChrome: { 10 | base: 'SauceLabs', 11 | browserName: 'chrome', 12 | version: 'latest' 13 | }, 14 | slChromeBeta: { 15 | base: 'SauceLabs', 16 | browserName: 'chrome', 17 | version: 'beta' 18 | }, 19 | slFirefox: { 20 | base: 'SauceLabs', 21 | browserName: 'firefox', 22 | version: 'latest' 23 | }, 24 | slFirefoxBeta: { 25 | base: 'SauceLabs', 26 | browserName: 'firefox', 27 | version: 'beta' 28 | }, 29 | slSafari: { 30 | base: 'SauceLabs', 31 | browserName: 'safari', 32 | platform: 'OS X 10.14', 33 | version: 'latest' 34 | }, 35 | slEdge: { 36 | base: 'SauceLabs', 37 | browserName: 'microsoftedge', 38 | platform: 'Windows 10', 39 | version: 'latest' 40 | }, 41 | slIE11: { 42 | base: 'SauceLabs', 43 | browserName: 'internet explorer', 44 | platform: 'Windows 8.1', 45 | version: '11' 46 | }, 47 | slIE10: { 48 | base: 'SauceLabs', 49 | browserName: 'internet explorer', 50 | platform: 'Windows 7', 51 | version: '10' 52 | }, 53 | slIE9: { 54 | base: 'SauceLabs', 55 | browserName: 'internet explorer', 56 | platform: 'Windows 7', 57 | version: '10', 58 | 'x-ua-compatible': 'IE=EmulateIE9' 59 | }, 60 | slIE8: { 61 | base: 'SauceLabs', 62 | browserName: 'internet explorer', 63 | platform: 'Windows 7', 64 | version: '10', 65 | 'x-ua-compatible': 'IE=EmulateIE8' 66 | } 67 | }; 68 | 69 | config.set({ 70 | basePath: '', 71 | frameworks: ['jasmine'], 72 | files: [ 73 | 'node_modules/stackframe/dist/stackframe.js', 74 | 'error-stack-parser.js', 75 | 'spec/fixtures/captured-errors.js', 76 | 'spec/spec-helper.js', 77 | 'spec/*-spec.js' 78 | ], 79 | exclude: [], 80 | port: 9876, 81 | colors: false, 82 | logLevel: config.LOG_INFO, 83 | autoWatch: false, 84 | browserDisconnectTimeout: 10000, 85 | browserDisconnectTolerance: 1, 86 | browserNoActivityTimeout: 240000, 87 | captureTimeout: 240000, 88 | sauceLabs: { 89 | testName: 'error-stack-parser unit tests', 90 | commandTimeout: 600, 91 | idleTimeout: 600, 92 | recordScreenshots: false, 93 | recordVideo: false, 94 | retryLimit: 3 95 | }, 96 | customLaunchers: customLaunchers, 97 | browsers: Object.keys(customLaunchers), 98 | reporters: ['dots', 'saucelabs', 'coverage', 'coveralls'], 99 | preprocessors: { 100 | 'error-stack-parser.js': 'coverage' 101 | }, 102 | coverageReporter: { 103 | type: 'lcov', 104 | dir: 'coverage', 105 | subdir: function(browser) { 106 | return browser.toLowerCase().split(/[ /-]/)[0]; 107 | } 108 | }, 109 | singleRun: true 110 | }); 111 | }; 112 | -------------------------------------------------------------------------------- /karma.conf.js: -------------------------------------------------------------------------------- 1 | module.exports = function(config) { 2 | config.set({ 3 | basePath: '', 4 | frameworks: ['jasmine'], 5 | files: [ 6 | 'node_modules/stackframe/dist/stackframe.js', 7 | 'error-stack-parser.js', 8 | 'spec/fixtures/captured-errors.js', 9 | 'spec/spec-helper.js', 10 | 'spec/*-spec.js' 11 | ], 12 | port: 9876, 13 | colors: true, 14 | logLevel: config.LOG_INFO, 15 | autoWatch: true, 16 | customLaunchers: { 17 | Chrome_No_Sandbox: { 18 | base: 'Chrome', 19 | flags: ['--no-sandbox'] 20 | } 21 | }, 22 | browsers: ['PhantomJS'], 23 | reporters: ['spec', 'saucelabs', 'coverage', 'coveralls'], 24 | preprocessors: { 25 | 'error-stack-parser.js': 'coverage' 26 | }, 27 | coverageReporter: { 28 | type: 'lcov', 29 | dir: 'coverage', 30 | subdir: function(browser) { 31 | return browser.toLowerCase().split(/[ /-]/)[0]; 32 | } 33 | }, 34 | singleRun: false 35 | }); 36 | }; 37 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "error-stack-parser", 3 | "description": "Extract meaning from JS Errors", 4 | "maintainers": [ 5 | "Eric Wendelin (https://www.eriwen.com)", 6 | "Victor Homyakov (https://github.com/victor-homyakov)", 7 | "Oliver Salzburg (https://github.com/oliversalzburg)", 8 | "Ben Gourley (https://github.com/bengourley)" 9 | ], 10 | "version": "2.1.4", 11 | "license": "MIT", 12 | "keywords": [ 13 | "stacktrace", 14 | "error", 15 | "stack", 16 | "parser" 17 | ], 18 | "homepage": "https://www.stacktracejs.com", 19 | "dependencies": { 20 | "stackframe": "^1.3.4" 21 | }, 22 | "repository": { 23 | "type": "git", 24 | "url": "git://github.com/stacktracejs/error-stack-parser.git" 25 | }, 26 | "devDependencies": { 27 | "eslint": "^8.17.0", 28 | "jasmine": "^4.1.0", 29 | "jasmine-core": "^4.1.1", 30 | "karma": "^6.3.20", 31 | "karma-chrome-launcher": "^3.1.1", 32 | "karma-coverage": "^2.2.0", 33 | "karma-coveralls": "^2.1.0", 34 | "karma-firefox-launcher": "^2.1.2", 35 | "karma-ie-launcher": "^1.0.0", 36 | "karma-jasmine": "^4.0.2", 37 | "karma-opera-launcher": "^1.0.0", 38 | "karma-phantomjs-launcher": "^1.0.4", 39 | "karma-safari-launcher": "^1.0.0", 40 | "karma-sauce-launcher": "^4.3.6", 41 | "karma-spec-reporter": "^0.0.34", 42 | "uglify-es": "^3.3.9" 43 | }, 44 | "bugs": { 45 | "url": "https://github.com/stacktracejs/error-stack-parser/issues" 46 | }, 47 | "main": "./error-stack-parser.js", 48 | "typings": "./error-stack-parser.d.ts", 49 | "files": [ 50 | "LICENSE", 51 | "README.md", 52 | "error-stack-parser.js", 53 | "error-stack-parser.d.ts", 54 | "dist/" 55 | ], 56 | "scripts": { 57 | "lint": "eslint --fix .", 58 | "test": "karma start karma.conf.js --single-run", 59 | "test-pr": "karma start karma.conf.js --single-run --browsers Firefox,Chrome_No_Sandbox", 60 | "test-ci": "karma start karma.conf.ci.js --single-run", 61 | "prepare": "cp error-stack-parser.js dist/ && uglifyjs node_modules/stackframe/stackframe.js error-stack-parser.js -o dist/error-stack-parser.min.js --compress --mangle --source-map \"url=error-stack-parser.min.js.map\"" 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /spec/error-stack-parser-spec.js: -------------------------------------------------------------------------------- 1 | /* global ErrorStackParser: false, CapturedExceptions: false */ 2 | describe('ErrorStackParser', function() { 3 | describe('#parse', function() { 4 | var unit = ErrorStackParser; 5 | it('should not parse IE 9 Error', function() { 6 | expect(function() { 7 | unit.parse(CapturedExceptions.IE_9); 8 | }).toThrow(new Error('Cannot parse given Error object')); 9 | }); 10 | 11 | it('should parse Safari 6 Error.stack', function() { 12 | var stackFrames = unit.parse(CapturedExceptions.SAFARI_6); 13 | expect(stackFrames).toBeTruthy(); 14 | expect(stackFrames.length).toBe(3); 15 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 48]); 16 | expect(stackFrames[1]).toMatchStackFrame(['dumpException3', undefined, 'http://path/to/file.js', 52]); 17 | expect(stackFrames[2]).toMatchStackFrame(['onclick', undefined, 'http://path/to/file.js', 82]); 18 | }); 19 | 20 | it('should parse Safari 7 Error.stack', function() { 21 | var stackFrames = unit.parse(CapturedExceptions.SAFARI_7); 22 | expect(stackFrames).toBeTruthy(); 23 | expect(stackFrames.length).toBe(3); 24 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 48, 22]); 25 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 52, 15]); 26 | expect(stackFrames[2]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 108, 107]); 27 | }); 28 | 29 | it('should parse Safari 8 Error.stack', function() { 30 | var stackFrames = unit.parse(CapturedExceptions.SAFARI_8); 31 | expect(stackFrames).toBeTruthy(); 32 | expect(stackFrames.length).toBe(3); 33 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 47, 22]); 34 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 52, 15]); 35 | expect(stackFrames[2]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 108, 23]); 36 | }); 37 | 38 | it('should parse nested eval() from Safari 9', function() { 39 | var stackFrames = unit.parse(CapturedExceptions.SAFARI_9_NESTED_EVAL); 40 | expect(stackFrames).toBeTruthy(); 41 | expect(stackFrames.length).toBe(5); 42 | expect(stackFrames[0]).toMatchStackFrame(['baz', undefined, undefined, undefined, undefined]); 43 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, undefined, undefined, undefined]); 44 | expect(stackFrames[2]).toMatchStackFrame(['eval code', undefined, undefined, undefined, undefined]); 45 | expect(stackFrames[3]).toMatchStackFrame(['speak', undefined, 'http://localhost:8080/file.js', 26, 21]); 46 | expect(stackFrames[4]).toMatchStackFrame(['global code', undefined, 'http://localhost:8080/file.js', 33, 18]); 47 | }); 48 | 49 | it('should parse Firefox 31 Error.stack', function() { 50 | var stackFrames = unit.parse(CapturedExceptions.FIREFOX_31); 51 | expect(stackFrames).toBeTruthy(); 52 | expect(stackFrames.length).toBe(2); 53 | expect(stackFrames[0]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 41, 13]); 54 | expect(stackFrames[1]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 1, 1]); 55 | }); 56 | 57 | it('should parse nested eval() from Firefox 43', function() { 58 | var stackFrames = unit.parse(CapturedExceptions.FIREFOX_43_NESTED_EVAL); 59 | expect(stackFrames).toBeTruthy(); 60 | expect(stackFrames.length).toBe(5); 61 | expect(stackFrames[0]).toMatchStackFrame(['baz', undefined, 'http://localhost:8080/file.js', 26, undefined]); 62 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://localhost:8080/file.js', 26, undefined]); 63 | expect(stackFrames[2]).toMatchStackFrame([undefined, undefined, 'http://localhost:8080/file.js', 26, undefined]); 64 | expect(stackFrames[3]).toMatchStackFrame(['speak', undefined, 'http://localhost:8080/file.js', 26, 17]); 65 | expect(stackFrames[4]).toMatchStackFrame([undefined, undefined, 'http://localhost:8080/file.js', 33, 9]); 66 | }); 67 | 68 | it('should parse function names containing @ in Firefox 43 Error.stack', function() { 69 | var stackFrames = unit.parse(CapturedExceptions.FIREFOX_43_FUNCTION_NAME_WITH_AT_SIGN); 70 | expect(stackFrames).toBeTruthy(); 71 | expect(stackFrames.length).toBe(2); 72 | expect(stackFrames[0]).toMatchStackFrame(['obj["@fn"]', undefined, 'Scratchpad/1', 10, 29]); 73 | expect(stackFrames[1]).toMatchStackFrame([undefined, undefined, 'Scratchpad/1', 11, 1]); 74 | }); 75 | 76 | it('should parse stack traces with @ in the URL', function() { 77 | var stackFrames = unit.parse(CapturedExceptions.FIREFOX_60_URL_WITH_AT_SIGN); 78 | expect(stackFrames).toBeTruthy(); 79 | expect(stackFrames.length).toBe(5); 80 | expect(stackFrames[0]).toMatchStackFrame(['who', undefined, 'http://localhost:5000/misc/@stuff/foo.js', 3, 9]); 81 | expect(stackFrames[1]).toMatchStackFrame(['what', undefined, 'http://localhost:5000/misc/@stuff/foo.js', 6, 3]); 82 | }); 83 | 84 | it('should parse stack traces with @ in the URL and the method', function() { 85 | var stackFrames = unit.parse(CapturedExceptions.FIREFOX_60_URL_AND_FUNCTION_NAME_WITH_AT_SIGN); 86 | expect(stackFrames).toBeTruthy(); 87 | expect(stackFrames.length).toBe(5); 88 | expect(stackFrames[0]).toMatchStackFrame(['obj["@who"]', undefined, 'http://localhost:5000/misc/@stuff/foo.js', 4, 9]); 89 | expect(stackFrames[1]).toMatchStackFrame(['what', undefined, 'http://localhost:5000/misc/@stuff/foo.js', 8, 3]); 90 | }); 91 | 92 | it('should parse V8 Error.stack', function() { 93 | var stackFrames = unit.parse(CapturedExceptions.CHROME_15); 94 | expect(stackFrames).toBeTruthy(); 95 | expect(stackFrames.length).toBe(4); 96 | expect(stackFrames[0]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 13, 17]); 97 | expect(stackFrames[1]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 16, 5]); 98 | expect(stackFrames[2]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 20, 5]); 99 | expect(stackFrames[3]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 24, 4]); 100 | }); 101 | 102 | it('should parse V8 entries with no location', function() { 103 | var stackFrames = unit.parse({stack: 'Error\n at Array.forEach (native)'}); 104 | expect(stackFrames.length).toBe(1); 105 | expect(stackFrames[0]).toMatchStackFrame(['Array.forEach', undefined, '(native)', undefined, undefined]); 106 | }); 107 | 108 | it('should parse V8 Error.stack entries with port numbers', function() { 109 | var stackFrames = unit.parse(CapturedExceptions.CHROME_36); 110 | expect(stackFrames).toBeTruthy(); 111 | expect(stackFrames.length).toBe(2); 112 | expect(stackFrames[0]).toMatchStackFrame(['dumpExceptionError', undefined, 'http://localhost:8080/file.js', 41, 27]); 113 | }); 114 | 115 | it('should parse error stacks with Constructors', function() { 116 | var stackFrames = unit.parse(CapturedExceptions.CHROME_46); 117 | expect(stackFrames).toBeTruthy(); 118 | expect(stackFrames.length).toBe(2); 119 | expect(stackFrames[0]).toMatchStackFrame(['new CustomError', undefined, 'http://localhost:8080/file.js', 41, 27]); 120 | expect(stackFrames[1]).toMatchStackFrame(['HTMLButtonElement.onclick', undefined, 'http://localhost:8080/file.js', 107, 146]); 121 | }); 122 | 123 | it('should parse nested eval() from V8', function() { 124 | var stackFrames = unit.parse(CapturedExceptions.CHROME_48_NESTED_EVAL); 125 | expect(stackFrames).toBeTruthy(); 126 | expect(stackFrames.length).toBe(5); 127 | expect(stackFrames[0]).toMatchStackFrame(['baz', undefined, 'http://localhost:8080/file.js', 21, 17]); 128 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://localhost:8080/file.js', 21, 17]); 129 | expect(stackFrames[2]).toMatchStackFrame(['eval', undefined, 'http://localhost:8080/file.js', 21, 17]); 130 | expect(stackFrames[3]).toMatchStackFrame(['Object.speak', undefined, 'http://localhost:8080/file.js', 21, 17]); 131 | expect(stackFrames[4]).toMatchStackFrame([undefined, undefined, 'http://localhost:8080/file.js', 31, 13]); 132 | }); 133 | 134 | it('should parse IE 10 Error stacks', function() { 135 | var stackFrames = unit.parse(CapturedExceptions.IE_10); 136 | expect(stackFrames).toBeTruthy(); 137 | expect(stackFrames.length).toBe(3); 138 | expect(stackFrames[0]).toMatchStackFrame(['Anonymous function', undefined, 'http://path/to/file.js', 48, 13]); 139 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 46, 9]); 140 | expect(stackFrames[2]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 82, 1]); 141 | }); 142 | 143 | it('should parse IE 11 Error stacks', function() { 144 | var stackFrames = unit.parse(CapturedExceptions.IE_11); 145 | expect(stackFrames).toBeTruthy(); 146 | expect(stackFrames.length).toBe(3); 147 | expect(stackFrames[0]).toMatchStackFrame(['Anonymous function', undefined, 'http://path/to/file.js', 47, 21]); 148 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 45, 13]); 149 | expect(stackFrames[2]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 108, 1]); 150 | }); 151 | 152 | it('should parse nested eval() from Edge', function() { 153 | var stackFrames = unit.parse(CapturedExceptions.EDGE_20_NESTED_EVAL); 154 | expect(stackFrames).toBeTruthy(); 155 | expect(stackFrames.length).toBe(5); 156 | expect(stackFrames[0]).toMatchStackFrame(['baz', undefined, undefined, 1, 18]); 157 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, undefined, 2, 90]); 158 | expect(stackFrames[2]).toMatchStackFrame(['eval', undefined, undefined, 4, 18]); 159 | expect(stackFrames[3]).toMatchStackFrame(['speak', undefined, 'http://localhost:8080/file.js', 25, 17]); 160 | expect(stackFrames[4]).toMatchStackFrame(['Global code', undefined, 'http://localhost:8080/file.js', 32, 9]); 161 | }); 162 | 163 | it('should parse Opera 9.27 Error messages', function() { 164 | var stackFrames = unit.parse(CapturedExceptions.OPERA_927); 165 | expect(stackFrames).toBeTruthy(); 166 | expect(stackFrames.length).toBe(3); 167 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 43]); 168 | expect(stackFrames[1]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 31]); 169 | expect(stackFrames[2]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 18]); 170 | }); 171 | 172 | it('should parse Opera 10 Error messages', function() { 173 | var stackFrames = unit.parse(CapturedExceptions.OPERA_10); 174 | expect(stackFrames).toBeTruthy(); 175 | expect(stackFrames.length).toBe(7); 176 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 42]); 177 | expect(stackFrames[1]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 27]); 178 | expect(stackFrames[2]).toMatchStackFrame(['printStackTrace', undefined, 'http://path/to/file.js', 18]); 179 | expect(stackFrames[3]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 4]); 180 | expect(stackFrames[4]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 7]); 181 | expect(stackFrames[5]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 11]); 182 | expect(stackFrames[6]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 15]); 183 | }); 184 | 185 | it('should parse Opera 11 Error messages', function() { 186 | var stackFrames = unit.parse(CapturedExceptions.OPERA_11); 187 | expect(stackFrames).toBeTruthy(); 188 | expect(stackFrames.length).toBe(4); 189 | expect(stackFrames[0]).toMatchStackFrame(['run', undefined, 'http://path/to/file.js', 27]); 190 | expect(stackFrames[1]).toMatchStackFrame(['bar', undefined, 'http://domain.com:1234/path/to/file.js', 18]); 191 | expect(stackFrames[2]).toMatchStackFrame(['foo', undefined, 'http://domain.com:1234/path/to/file.js', 11]); 192 | expect(stackFrames[3]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 15]); 193 | }); 194 | 195 | it('should parse Opera 25 Error stacks', function() { 196 | var stackFrames = unit.parse(CapturedExceptions.OPERA_25); 197 | expect(stackFrames).toBeTruthy(); 198 | expect(stackFrames.length).toBe(3); 199 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 47, 22]); 200 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 52, 15]); 201 | expect(stackFrames[2]).toMatchStackFrame(['bar', undefined, 'http://path/to/file.js', 108, 168]); 202 | }); 203 | 204 | it('should handle newlines in Error stack messages', function() { 205 | var stackFrames = unit.parse({ 206 | stack: 'Error: Problem at this\nlocation. Error code:1234\n' + 207 | ' at http://path/to/file.js:47:22\n' + 208 | ' at foo (http://path/to/file.js:52:15)' 209 | }); 210 | 211 | expect(stackFrames.length).toBe(2); 212 | expect(stackFrames[0]).toMatchStackFrame([undefined, undefined, 'http://path/to/file.js', 47, 22]); 213 | expect(stackFrames[1]).toMatchStackFrame(['foo', undefined, 'http://path/to/file.js', 52, 15]); 214 | }); 215 | 216 | it('should handle webpack eval stacks', function() { 217 | var stackframes = unit.parse({stack: 'ReferenceError: chilxdren is not defined\n ' + 218 | 'at Layout (eval at proxyClass (webpack:///../react-hot-loader/~/react-proxy/modules/createClassProxy.js?), :4:17)' 219 | }); 220 | expect(stackframes.length).toBe(1); 221 | expect(stackframes[0].fileName).toEqual('webpack:///../react-hot-loader/~/react-proxy/modules/createClassProxy.js?'); 222 | expect(stackframes[0].lineNumber).toBeUndefined(); 223 | expect(stackframes[0].columnNumber).toBeUndefined(); 224 | }); 225 | 226 | it('should handle spaces in Node.js stacks', function() { 227 | var stackframes = unit.parse(CapturedExceptions.NODE_WITH_SPACES); 228 | expect(stackframes.length).toBe(8); 229 | expect(stackframes[0].fileName).toEqual('/var/app/scratch/my project/index.js'); 230 | expect(stackframes[0].lineNumber).toBe(2); 231 | expect(stackframes[0].columnNumber).toBe(9); 232 | expect(stackframes[1].fileName).toEqual('/var/app/scratch/my project/index.js'); 233 | expect(stackframes[1].lineNumber).toBe(2); 234 | expect(stackframes[1].columnNumber).toBe(9); 235 | }); 236 | }); 237 | }); 238 | -------------------------------------------------------------------------------- /spec/fixtures/captured-errors.js: -------------------------------------------------------------------------------- 1 | // jscs:disable disallowImplicitTypeConversion 2 | // jscs:disable maximumLineLength 3 | /* exported CapturedExceptions */ 4 | var CapturedExceptions = {}; 5 | 6 | CapturedExceptions.OPERA_854 = { 7 | message: 'Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n' + 8 | 'Backtrace:\n' + 9 | ' Line 44 of linked script http://path/to/file.js\n' + 10 | ' this.undef();\n' + 11 | ' Line 31 of linked script http://path/to/file.js\n' + 12 | ' ex = ex || this.createException();\n' + 13 | ' Line 18 of linked script http://path/to/file.js\n' + 14 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 15 | ' Line 4 of inline#1 script in http://path/to/file.js\n' + 16 | ' printTrace(printStackTrace());\n' + 17 | ' Line 7 of inline#1 script in http://path/to/file.js\n' + 18 | ' bar(n - 1);\n' + 19 | ' Line 11 of inline#1 script in http://path/to/file.js\n' + 20 | ' bar(2);\n' + 21 | ' Line 15 of inline#1 script in http://path/to/file.js\n' + 22 | ' foo();\n' + 23 | '', 24 | 'opera#sourceloc': 44 25 | }; 26 | 27 | CapturedExceptions.OPERA_902 = { 28 | message: 'Statement on line 44: Type mismatch (usually a non-object value used where an object is required)\n' + 29 | 'Backtrace:\n' + 30 | ' Line 44 of linked script http://path/to/file.js\n' + 31 | ' this.undef();\n' + 32 | ' Line 31 of linked script http://path/to/file.js\n' + 33 | ' ex = ex || this.createException();\n' + 34 | ' Line 18 of linked script http://path/to/file.js\n' + 35 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 36 | ' Line 4 of inline#1 script in http://path/to/file.js\n' + 37 | ' printTrace(printStackTrace());\n' + 38 | ' Line 7 of inline#1 script in http://path/to/file.js\n' + 39 | ' bar(n - 1);\n' + 40 | ' Line 11 of inline#1 script in http://path/to/file.js\n' + 41 | ' bar(2);\n' + 42 | ' Line 15 of inline#1 script in http://path/to/file.js\n' + 43 | ' foo();\n' + 44 | '', 45 | 'opera#sourceloc': 44 46 | }; 47 | 48 | CapturedExceptions.OPERA_927 = { 49 | message: 'Statement on line 43: Type mismatch (usually a non-object value used where an object is required)\n' + 50 | 'Backtrace:\n' + 51 | ' Line 43 of linked script http://path/to/file.js\n' + 52 | ' bar(n - 1);\n' + 53 | ' Line 31 of linked script http://path/to/file.js\n' + 54 | ' bar(2);\n' + 55 | ' Line 18 of linked script http://path/to/file.js\n' + 56 | ' foo();\n' + 57 | '', 58 | 'opera#sourceloc': 43 59 | }; 60 | 61 | CapturedExceptions.OPERA_964 = { 62 | message: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)\n' + 63 | 'Backtrace:\n' + 64 | ' Line 42 of linked script http://path/to/file.js\n' + 65 | ' this.undef();\n' + 66 | ' Line 27 of linked script http://path/to/file.js\n' + 67 | ' ex = ex || this.createException();\n' + 68 | ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + 69 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 70 | ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + 71 | ' printTrace(printStackTrace());\n' + 72 | ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + 73 | ' bar(n - 1);\n' + 74 | ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + 75 | ' bar(2);\n' + 76 | ' Line 15 of inline#1 script in http://path/to/file.js\n' + 77 | ' foo();\n' + 78 | '', 79 | 'opera#sourceloc': 42, 80 | stacktrace: ' ... Line 27 of linked script http://path/to/file.js\n' + 81 | ' ex = ex || this.createException();\n' + 82 | ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + 83 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 84 | ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + 85 | ' printTrace(printStackTrace());\n' + 86 | ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + 87 | ' bar(n - 1);\n' + 88 | ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + 89 | ' bar(2);\n' + 90 | ' Line 15 of inline#1 script in http://path/to/file.js\n' + 91 | ' foo();\n' + 92 | '' 93 | }; 94 | 95 | CapturedExceptions.OPERA_10 = { 96 | message: 'Statement on line 42: Type mismatch (usually non-object value supplied where object required)', 97 | 'opera#sourceloc': 42, 98 | stacktrace: ' Line 42 of linked script http://path/to/file.js\n' + 99 | ' this.undef();\n' + 100 | ' Line 27 of linked script http://path/to/file.js\n' + 101 | ' ex = ex || this.createException();\n' + 102 | ' Line 18 of linked script http://path/to/file.js: In function printStackTrace\n' + 103 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 104 | ' Line 4 of inline#1 script in http://path/to/file.js: In function bar\n' + 105 | ' printTrace(printStackTrace());\n' + 106 | ' Line 7 of inline#1 script in http://path/to/file.js: In function bar\n' + 107 | ' bar(n - 1);\n' + 108 | ' Line 11 of inline#1 script in http://path/to/file.js: In function foo\n' + 109 | ' bar(2);\n' + 110 | ' Line 15 of inline#1 script in http://path/to/file.js\n' + 111 | ' foo();\n' + 112 | '' 113 | }; 114 | 115 | CapturedExceptions.OPERA_11 = { 116 | message: '\'this.undef\' is not a function', 117 | stack: '([arguments not available])@http://path/to/file.js:27\n' + 118 | 'bar([arguments not available])@http://domain.com:1234/path/to/file.js:18\n' + 119 | 'foo([arguments not available])@http://domain.com:1234/path/to/file.js:11\n' + 120 | '@http://path/to/file.js:15\n' + 121 | 'Error created at @http://path/to/file.js:15', 122 | stacktrace: 'Error thrown at line 42, column 12 in () in http://path/to/file.js:\n' + 123 | ' this.undef();\n' + 124 | 'called from line 27, column 8 in (ex) in http://path/to/file.js:\n' + 125 | ' ex = ex || this.createException();\n' + 126 | 'called from line 18, column 4 in printStackTrace(options) in http://path/to/file.js:\n' + 127 | ' var p = new printStackTrace.implementation(), result = p.run(ex);\n' + 128 | 'called from line 4, column 5 in bar(n) in http://path/to/file.js:\n' + 129 | ' printTrace(printStackTrace());\n' + 130 | 'called from line 7, column 4 in bar(n) in http://path/to/file.js:\n' + 131 | ' bar(n - 1);\n' + 132 | 'called from line 11, column 4 in foo() in http://path/to/file.js:\n' + 133 | ' bar(2);\n' + 134 | 'called from line 15, column 3 in http://path/to/file.js:\n' + 135 | ' foo();' 136 | }; 137 | 138 | CapturedExceptions.OPERA_12 = { 139 | message: 'Cannot convert \'x\' to object', 140 | stack: '([arguments not available])@http://localhost:8000/ExceptionLab.html:48\n' + 141 | 'dumpException3([arguments not available])@http://localhost:8000/ExceptionLab.html:46\n' + 142 | '([arguments not available])@http://localhost:8000/ExceptionLab.html:1', 143 | stacktrace: 'Error thrown at line 48, column 12 in (x) in http://localhost:8000/ExceptionLab.html:\n' + 144 | ' x.undef();\n' + 145 | 'called from line 46, column 8 in dumpException3() in http://localhost:8000/ExceptionLab.html:\n' + 146 | ' dumpException((function(x) {\n' + 147 | 'called from line 1, column 0 in (event) in http://localhost:8000/ExceptionLab.html:\n' + 148 | ' dumpException3();' 149 | }; 150 | 151 | CapturedExceptions.OPERA_25 = { 152 | message: 'Cannot read property \'undef\' of null', 153 | name: 'TypeError', 154 | stack: 'TypeError: Cannot read property \'undef\' of null\n' + 155 | ' at http://path/to/file.js:47:22\n' + 156 | ' at foo (http://path/to/file.js:52:15)\n' + 157 | ' at bar (http://path/to/file.js:108:168)' 158 | }; 159 | 160 | CapturedExceptions.CHROME_15 = { 161 | 'arguments': ['undef'], 162 | message: 'Object # has no method \'undef\'', 163 | stack: 'TypeError: Object # has no method \'undef\'\n' + 164 | ' at bar (http://path/to/file.js:13:17)\n' + 165 | ' at bar (http://path/to/file.js:16:5)\n' + 166 | ' at foo (http://path/to/file.js:20:5)\n' + 167 | ' at http://path/to/file.js:24:4' 168 | }; 169 | 170 | CapturedExceptions.CHROME_36 = { 171 | message: 'Default error', 172 | name: 'Error', 173 | stack: 'Error: Default error\n' + 174 | ' at dumpExceptionError (http://localhost:8080/file.js:41:27)\n' + 175 | ' at HTMLButtonElement.onclick (http://localhost:8080/file.js:107:146)' 176 | }; 177 | 178 | CapturedExceptions.CHROME_46 = { 179 | message: 'Default error', 180 | name: 'Error', 181 | stack: 'Error: Default error\n' + 182 | ' at new CustomError (http://localhost:8080/file.js:41:27)\n' + 183 | ' at HTMLButtonElement.onclick (http://localhost:8080/file.js:107:146)' 184 | }; 185 | 186 | CapturedExceptions.CHROME_48_NESTED_EVAL = { 187 | message: 'message string', 188 | name: 'Error', 189 | stack: 'Error: message string\n' + 190 | 'at baz (eval at foo (eval at speak (http://localhost:8080/file.js:21:17)), :1:30)\n' + 191 | 'at foo (eval at speak (http://localhost:8080/file.js:21:17), :2:96)\n' + 192 | 'at eval (eval at speak (http://localhost:8080/file.js:21:17), :4:18)\n' + 193 | 'at Object.speak (http://localhost:8080/file.js:21:17)\n' + 194 | 'at http://localhost:8080/file.js:31:13\n' 195 | }; 196 | 197 | CapturedExceptions.FIREFOX_3 = { 198 | fileName: 'http://127.0.0.1:8000/js/stacktrace.js', 199 | lineNumber: 44, 200 | message: 'this.undef is not a function', 201 | name: 'TypeError', 202 | stack: '()@http://127.0.0.1:8000/js/stacktrace.js:44\n' + 203 | '(null)@http://127.0.0.1:8000/js/stacktrace.js:31\n' + 204 | 'printStackTrace()@http://127.0.0.1:8000/js/stacktrace.js:18\n' + 205 | 'bar(1)@http://127.0.0.1:8000/js/file.js:13\n' + 206 | 'bar(2)@http://127.0.0.1:8000/js/file.js:16\n' + 207 | 'foo()@http://127.0.0.1:8000/js/file.js:20\n' + 208 | '@http://127.0.0.1:8000/js/file.js:24\n' + 209 | '' 210 | }; 211 | 212 | CapturedExceptions.FIREFOX_7 = { 213 | fileName: 'file:///G:/js/stacktrace.js', 214 | lineNumber: 44, 215 | stack: '()@file:///G:/js/stacktrace.js:44\n' + 216 | '(null)@file:///G:/js/stacktrace.js:31\n' + 217 | 'printStackTrace()@file:///G:/js/stacktrace.js:18\n' + 218 | 'bar(1)@file:///G:/js/file.js:13\n' + 219 | 'bar(2)@file:///G:/js/file.js:16\n' + 220 | 'foo()@file:///G:/js/file.js:20\n' + 221 | '@file:///G:/js/file.js:24\n' + 222 | '' 223 | }; 224 | 225 | CapturedExceptions.FIREFOX_14 = { 226 | message: 'x is null', 227 | stack: '@http://path/to/file.js:48\n' + 228 | 'dumpException3@http://path/to/file.js:52\n' + 229 | 'onclick@http://path/to/file.js:1\n' + 230 | '', 231 | fileName: 'http://path/to/file.js', 232 | lineNumber: 48 233 | }; 234 | 235 | CapturedExceptions.FIREFOX_31 = { 236 | message: 'Default error', 237 | name: 'Error', 238 | stack: 'foo@http://path/to/file.js:41:13\n' + 239 | 'bar@http://path/to/file.js:1:1\n' + 240 | '', 241 | fileName: 'http://path/to/file.js', 242 | lineNumber: 41, 243 | columnNumber: 12 244 | }; 245 | 246 | CapturedExceptions.FIREFOX_43_NESTED_EVAL = { 247 | columnNumber: 30, 248 | fileName: 'http://localhost:8080/file.js line 25 > eval line 2 > eval', 249 | lineNumber: 1, 250 | message: 'message string', 251 | stack: 'baz@http://localhost:8080/file.js line 26 > eval line 2 > eval:1:30\n' + 252 | 'foo@http://localhost:8080/file.js line 26 > eval:2:96\n' + 253 | '@http://localhost:8080/file.js line 26 > eval:4:18\n' + 254 | 'speak@http://localhost:8080/file.js:26:17\n' + 255 | '@http://localhost:8080/file.js:33:9' 256 | }; 257 | 258 | CapturedExceptions.FIREFOX_43_FUNCTION_NAME_WITH_AT_SIGN = { 259 | message: 'Dummy error', 260 | name: 'Error', 261 | stack: 'obj["@fn"]@Scratchpad/1:10:29\n' + 262 | '@Scratchpad/1:11:1\n' + 263 | '', 264 | fileName: 'Scratchpad/1', 265 | lineNumber: 10, 266 | columnNumber: 29 267 | }; 268 | 269 | CapturedExceptions.FIREFOX_60_URL_WITH_AT_SIGN = { 270 | message: 'culprit', 271 | name: 'Error', 272 | stack: 'who@http://localhost:5000/misc/@stuff/foo.js:3:9\n' + 273 | 'what@http://localhost:5000/misc/@stuff/foo.js:6:3\n' + 274 | 'where@http://localhost:5000/misc/@stuff/foo.js:9:3\n' + 275 | 'why@http://localhost:5000/misc/@stuff/foo.js:12:3\n' + 276 | '@http://localhost:5000/misc/@stuff/foo.js:15:1\n', 277 | fileName: 'http://localhost:5000/misc/@stuff/foo.js', 278 | lineNumber: 3, 279 | columnNumber: 9 280 | }; 281 | 282 | CapturedExceptions.FIREFOX_60_URL_AND_FUNCTION_NAME_WITH_AT_SIGN = { 283 | message: 'culprit', 284 | name: 'Error', 285 | stack: 'obj["@who"]@http://localhost:5000/misc/@stuff/foo.js:4:9\n' + 286 | 'what@http://localhost:5000/misc/@stuff/foo.js:8:3\n' + 287 | 'where@http://localhost:5000/misc/@stuff/foo.js:11:3\n' + 288 | 'why@http://localhost:5000/misc/@stuff/foo.js:14:3\n' + 289 | '@http://localhost:5000/misc/@stuff/foo.js:17:1\n', 290 | fileName: 'http://localhost:5000/misc/@stuff/foo.js', 291 | lineNumber: 4, 292 | columnNumber: 9 293 | }; 294 | 295 | CapturedExceptions.SAFARI_6 = { 296 | message: '\'null\' is not an object (evaluating \'x.undef\')', 297 | stack: '@http://path/to/file.js:48\n' + 298 | 'dumpException3@http://path/to/file.js:52\n' + 299 | 'onclick@http://path/to/file.js:82\n' + 300 | '[native code]', 301 | line: 48, 302 | sourceURL: 'http://path/to/file.js' 303 | }; 304 | 305 | CapturedExceptions.SAFARI_7 = { 306 | message: '\'null\' is not an object (evaluating \'x.undef\')', 307 | name: 'TypeError', 308 | stack: 'http://path/to/file.js:48:22\n' + 309 | 'foo@http://path/to/file.js:52:15\n' + 310 | 'bar@http://path/to/file.js:108:107', 311 | line: 47, 312 | sourceURL: 'http://path/to/file.js' 313 | }; 314 | 315 | CapturedExceptions.SAFARI_8 = { 316 | message: 'null is not an object (evaluating \'x.undef\')', 317 | name: 'TypeError', 318 | stack: 'http://path/to/file.js:47:22\n' + 319 | 'foo@http://path/to/file.js:52:15\n' + 320 | 'bar@http://path/to/file.js:108:23', 321 | line: 47, 322 | column: 22, 323 | sourceURL: 'http://path/to/file.js' 324 | }; 325 | 326 | CapturedExceptions.SAFARI_8_EVAL = { 327 | message: 'Can\'t find variable: getExceptionProps', 328 | name: 'ReferenceError', 329 | stack: 'eval code\n' + 330 | 'eval@[native code]\n' + 331 | 'foo@http://path/to/file.js:58:21\n' + 332 | 'bar@http://path/to/file.js:109:91', 333 | line: 1, 334 | column: 18 335 | }; 336 | 337 | CapturedExceptions.SAFARI_9_NESTED_EVAL = { 338 | column: 39, 339 | line: 1, 340 | message: 'message string', 341 | stack: 'baz\n' + 342 | 'foo\n' + 343 | 'eval code\n' + 344 | 'eval@[native code]\n' + 345 | 'speak@http://localhost:8080/file.js:26:21\n' + 346 | 'global code@http://localhost:8080/file.js:33:18' 347 | }; 348 | 349 | CapturedExceptions.IE_9 = { 350 | message: 'Unable to get property \'undef\' of undefined or null reference', 351 | description: 'Unable to get property \'undef\' of undefined or null reference' 352 | }; 353 | 354 | CapturedExceptions.IE_10 = { 355 | message: 'Unable to get property \'undef\' of undefined or null reference', 356 | stack: 'TypeError: Unable to get property \'undef\' of undefined or null reference\n' + 357 | ' at Anonymous function (http://path/to/file.js:48:13)\n' + 358 | ' at foo (http://path/to/file.js:46:9)\n' + 359 | ' at bar (http://path/to/file.js:82:1)', 360 | description: 'Unable to get property \'undef\' of undefined or null reference', 361 | number: -2146823281 362 | }; 363 | 364 | CapturedExceptions.IE_11 = { 365 | message: 'Unable to get property \'undef\' of undefined or null reference', 366 | name: 'TypeError', 367 | stack: 'TypeError: Unable to get property \'undef\' of undefined or null reference\n' + 368 | ' at Anonymous function (http://path/to/file.js:47:21)\n' + 369 | ' at foo (http://path/to/file.js:45:13)\n' + 370 | ' at bar (http://path/to/file.js:108:1)', 371 | description: 'Unable to get property \'undef\' of undefined or null reference', 372 | number: -2146823281 373 | }; 374 | 375 | CapturedExceptions.EDGE_20_NESTED_EVAL = { 376 | description: 'message string', 377 | message: 'message string', 378 | name: 'Error', 379 | stack: 'Error: message string\n' + 380 | ' at baz (eval code:1:18)\n' + 381 | ' at foo (eval code:2:90)\n' + 382 | ' at eval code (eval code:4:18)\n' + 383 | ' at speak (http://localhost:8080/file.js:25:17)\n' + 384 | ' at Global code (http://localhost:8080/file.js:32:9)' 385 | }; 386 | 387 | CapturedExceptions.NODE_WITH_SPACES = { 388 | name: 'Error', 389 | message: '', 390 | stack: 'Error\n at /var/app/scratch/my '+ 391 | 'project/index.js:2:9\n at Object. ' + 392 | '(/var/app/scratch/my ' + 393 | 'project/index.js:2:9)\n at Module._compile ' + 394 | '(internal/modules/cjs/loader.js:774:30)\n at ' + 395 | 'Object.Module._extensions..js (internal/modules/cjs/loader.js:785:10)\n ' + 396 | ' at Module.load (internal/modules/cjs/loader.js:641:32)\n at ' + 397 | 'Function.Module._load (internal/modules/cjs/loader.js:556:12)\n at ' + 398 | 'Function.Module.runMain (internal/modules/cjs/loader.js:837:10)\n at ' + 399 | 'internal/main/run_main_module.js:17:11' 400 | }; 401 | -------------------------------------------------------------------------------- /spec/spec-helper.js: -------------------------------------------------------------------------------- 1 | // Polyfill for old browsers 2 | // See https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/isArray 3 | if (!Array.isArray) { 4 | Array.isArray = function(arg) { 5 | return Object.prototype.toString.call(arg) === '[object Array]'; 6 | }; 7 | } 8 | 9 | beforeEach(function() { 10 | jasmine.addMatchers({ 11 | toMatchStackFrame: function() { 12 | return { 13 | compare: function(actual, expected) { 14 | var message = ''; 15 | if (actual.getFunctionName() !== expected[0]) { 16 | message += 'expected functionName: ' + actual.getFunctionName() + ' to equal ' + expected[0] + '\n'; 17 | } 18 | if (Array.isArray(actual.getArgs()) && Array.isArray(expected[1])) { 19 | if (actual.getArgs().join() !== expected[1].join()) { 20 | message += 'expected args: ' + actual.getArgs() + ' to equal ' + expected[1] + '\n'; 21 | } 22 | } else if (actual.getArgs() !== expected[1]) { 23 | message += 'expected args: ' + actual.getArgs() + ' to equal ' + expected[1] + '\n'; 24 | } 25 | if (actual.getFileName() !== expected[2]) { 26 | message += 'expected fileName: ' + actual.getFileName() + ' to equal ' + expected[2] + '\n'; 27 | } 28 | if (actual.getLineNumber() !== expected[3]) { 29 | message += 'expected lineNumber: ' + actual.getLineNumber() + ' to equal ' + expected[3] + '\n'; 30 | } 31 | if (actual.getColumnNumber() !== expected[4]) { 32 | message += 'expected columnNumber: ' + actual.getColumnNumber() + ' to equal ' + expected[4] + '\n'; 33 | } 34 | return {pass: message === '', message: message}; 35 | } 36 | }; 37 | } 38 | }); 39 | }); 40 | 41 | 42 | 43 | --------------------------------------------------------------------------------