├── .gitignore ├── .jshintrc ├── .travis.yml ├── CONTRIBUTING.md ├── LICENSE ├── README.md ├── bower.json ├── browser.js ├── example.png ├── gulpfile.js ├── lib ├── formatting.js ├── index.js ├── normalization.js └── parsing.js ├── package.json └── test ├── formatting.js ├── index.js ├── normalization.js └── parsing.js /.gitignore: -------------------------------------------------------------------------------- 1 | /.coverdata/ 2 | /debug/ 3 | /node_modules/ 4 | /.coverrun 5 | /coverage.html 6 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "boss": true, 3 | "expr": true, 4 | "indent": 2, 5 | "lastsemic": true, 6 | "maxlen": 100, 7 | "newcap": false, 8 | "node": true, 9 | "onecase": true, 10 | "quotmark": "single", 11 | "strict": true, 12 | "undef": true, 13 | "unused": "vars", 14 | "validthis": true, 15 | "predef": [ 16 | "after", 17 | "afterEach", 18 | "before", 19 | "beforeEach", 20 | "describe", 21 | "it" 22 | ] 23 | } 24 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | sudo: false 3 | node_js: 4 | - "0.10" 5 | - "0.12" 6 | - "4" 7 | - "5" 8 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Reactive Development 2 | 3 | This project makes use of file watching to give you a reactive development 4 | workflow: 5 | 6 | 1. Start `gulp` (or `gulp watch`). 7 | 2. Make changes to code. 8 | 3. React to notifications. 9 | 10 | Any time you change a file, appropriate tests will be run for that file. 11 | 12 | # CLA 13 | 14 | Before your patches will be accepted, you'll need to agree to one of the CLAs.See http://polymer.github.io/CONTRIBUTORS.txt. 15 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014 The Polymer Authors. All rights reserved. 2 | 3 | Redistribution and use in source and binary forms, with or without 4 | modification, are permitted provided that the following conditions are 5 | met: 6 | 7 | * Redistributions of source code must retain the above copyright 8 | notice, this list of conditions and the following disclaimer. 9 | * Redistributions in binary form must reproduce the above 10 | copyright notice, this list of conditions and the following disclaimer 11 | in the documentation and/or other materials provided with the 12 | distribution. 13 | * Neither the name of Google Inc. nor the names of its 14 | contributors may be used to endorse or promote products derived from 15 | this software without specific prior written permission. 16 | 17 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS 18 | "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT 19 | LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR 20 | A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT 21 | OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, 22 | SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT 23 | LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 24 | DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 25 | THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 26 | (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![NPM version](http://img.shields.io/npm/v/stacky.svg)](https://npmjs.org/package/stacky) 2 | [![Build Status](http://img.shields.io/travis/PolymerLabs/stacky.svg)](https://travis-ci.org/PolymerLabs/stacky) 3 | 4 | # Stacky 5 | 6 | ## Formatting 7 | 8 | `stacky.pretty(error.stack)`: 9 | 10 | ![Example Pretty Stack](example.png?raw=true) 11 | 12 | `pretty` Provides [several options](lib/formatting.js#L15-L36) allowing you to 13 | tweak the output format to your liking. 14 | 15 | 16 | ## Parsing 17 | 18 | `stacky.parse(error.stack)`: 19 | 20 | ```js 21 | [ 22 | { 23 | method: 'thingCalled', 24 | location: 'some/file.js', 25 | line: 1, 26 | column: 2, 27 | } 28 | ... 29 | ] 30 | ``` 31 | -------------------------------------------------------------------------------- /bower.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stacky", 3 | "description": "Stacky parses stack traces from various sources, and formats them in readable ways.", 4 | "main": "browser.js", 5 | "authors": [ 6 | "The Polymer Authors" 7 | ], 8 | "ignore": [ 9 | "*", 10 | "!/lib/*.js", 11 | "!/LICENSE", 12 | "!/browser.js" 13 | ], 14 | "license": "BSD-3-Clause", 15 | "keywords": [ 16 | "stack", 17 | "trace", 18 | "error", 19 | "stack parser", 20 | "trace parser" 21 | ], 22 | "homepage": "https://github.com/PolymerLabs/stacky", 23 | "moduleType": [ 24 | "amd", 25 | "globals" 26 | ], 27 | "version": "1.3.2" 28 | } 29 | -------------------------------------------------------------------------------- /browser.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.Stacky = f()}})(function(){var define,module,exports;return (function e(t,n,r){function s(o,u){if(!n[o]){if(!t[o]){var a=typeof require=="function"&&require;if(!u&&a)return a(o,!0);if(i)return i(o,!0);var f=new Error("Cannot find module '"+o+"'");throw f.code="MODULE_NOT_FOUND",f}var l=n[o]={exports:{}};t[o][0].call(l.exports,function(e){var n=t[o][1][e];return s(n?n:e)},l,l.exports,e,t,n,r)}return n[o].exports}var i=typeof require=="function"&&require;for(var o=0;o', 24 | // A list of Strings/RegExps that will be stripped from `location` values on 25 | // each line (via `String#replace`). 26 | locationStrip: [], 27 | // A list of Strings/RegExps that indicate that a line is *not* important, and 28 | // should be styled as such. 29 | unimportantLocation: [], 30 | // A filter function to completely remove lines 31 | filter: function() { return false; }, 32 | // styles are functions that take a string and return that string when styled. 33 | styles: { 34 | method: passthrough, 35 | location: passthrough, 36 | line: passthrough, 37 | column: passthrough, 38 | unimportant: passthrough, 39 | }, 40 | }; 41 | 42 | // See Tero Tolonen's answer at 43 | // http://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser 44 | /*jshint -W054 */ 45 | var isNode = new Function('try {return this===global;}catch(e){return false;}'); 46 | 47 | // For Stacky-in-Node, we default to colored stacks. 48 | if (isNode()) { 49 | var chalk = require('chalk'); 50 | 51 | scope.defaults.styles = { 52 | method: chalk.magenta, 53 | location: chalk.blue, 54 | line: chalk.cyan, 55 | column: chalk.cyan, 56 | unimportant: chalk.dim 57 | }; 58 | } 59 | 60 | 61 | function pretty(stackOrParsed, options) { 62 | options = mergeDefaults(options || {}, scope.defaults); 63 | var lines = Array.isArray(stackOrParsed) ? stackOrParsed : parse(stackOrParsed); 64 | lines = clean(lines, options); 65 | 66 | var padSize = methodPadding(lines, options); 67 | var parts = lines.map(function(line) { 68 | var method = line.method || options.methodPlaceholder; 69 | var pad = options.indent + padding(padSize - method.length); 70 | 71 | var locationBits = [ 72 | options.styles.location(line.location), 73 | options.styles.line(line.line), 74 | ]; 75 | if ('column' in line) { 76 | locationBits.push(options.styles.column(line.column)); 77 | } 78 | var location = locationBits.join(':'); 79 | 80 | var text = pad + options.styles.method(method) + ' at ' + location; 81 | if (!line.important) { 82 | text = options.styles.unimportant(text); 83 | } 84 | return text; 85 | }); 86 | 87 | return parts.join('\n'); 88 | } 89 | 90 | function clean(lines, options) { 91 | var result = []; 92 | for (var i = 0, line; line = lines[i]; i++) { 93 | if (options.filter(line)) continue; 94 | line.location = cleanLocation(line.location, options); 95 | line.important = isImportant(line, options); 96 | result.push(line); 97 | } 98 | 99 | return result; 100 | } 101 | 102 | // Utility 103 | 104 | function passthrough(string) { 105 | return string; 106 | } 107 | 108 | function mergeDefaults(options, defaults) { 109 | var result = Object.create(defaults); 110 | Object.keys(options).forEach(function(key) { 111 | var value = options[key]; 112 | if (typeof value === 'object' && !Array.isArray(value)) { 113 | value = mergeDefaults(value, defaults[key]); 114 | } 115 | result[key] = value; 116 | }); 117 | return result; 118 | } 119 | 120 | function methodPadding(lines, options) { 121 | var size = options.methodPlaceholder.length; 122 | for (var i = 0, line; line = lines[i]; i++) { 123 | size = Math.min(options.maxMethodPadding, Math.max(size, line.method.length)); 124 | } 125 | return size; 126 | } 127 | 128 | function padding(length) { 129 | var result = ''; 130 | for (var i = 0; i < length; i++) { 131 | result = result + ' '; 132 | } 133 | return result; 134 | } 135 | 136 | function cleanLocation(location, options) { 137 | if (options.locationStrip) { 138 | for (var i = 0, matcher; matcher = options.locationStrip[i]; i++) { 139 | location = location.replace(matcher, ''); 140 | } 141 | } 142 | 143 | return location; 144 | } 145 | 146 | function isImportant(line, options) { 147 | if (options.unimportantLocation) { 148 | for (var i = 0, matcher; matcher = options.unimportantLocation[i]; i++) { 149 | if (line.location.match(matcher)) return false; 150 | } 151 | } 152 | 153 | return true; 154 | } 155 | 156 | scope.clean = clean; 157 | scope.pretty = pretty; 158 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 159 | 160 | 161 | },{"./parsing":4,"chalk":undefined}],2:[function(require,module,exports){ 162 | /** 163 | * @license 164 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 165 | * 166 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 167 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 168 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 169 | * Code distributed by Google as part of the polymer project is also subject to 170 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 171 | */ 172 | 'use strict'; 173 | 174 | var formatting = require('./formatting'); 175 | var normalization = require('./normalization'); 176 | var parsing = require('./parsing'); 177 | 178 | module.exports = { 179 | // Shorthands for your convenience. 180 | normalize: normalization.normalize, 181 | parse: parsing.parse, 182 | pretty: formatting.pretty, 183 | // Or the full modules. 184 | formatting: formatting, 185 | normalization: normalization, 186 | parsing: parsing, 187 | }; 188 | 189 | },{"./formatting":1,"./normalization":3,"./parsing":4}],3:[function(require,module,exports){ 190 | /** 191 | * @license 192 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 193 | * 194 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 195 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 196 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 197 | * Code distributed by Google as part of the polymer project is also subject to 198 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 199 | */ 200 | (function(scope) { 201 | 'use strict'; 202 | 203 | var parse = scope.parse || require('./parsing').parse; 204 | var pretty = scope.pretty || require('./formatting').pretty; 205 | 206 | function normalize(error, prettyOptions) { 207 | if (error.parsedStack) return error; 208 | var message = error.message || error.description || error || ''; 209 | var parsedStack = []; 210 | try { 211 | parsedStack = parse(error.stack || error.toString()); 212 | } catch (error) { 213 | // Ah well. 214 | } 215 | 216 | if (parsedStack.length === 0 && error.fileName) { 217 | parsedStack.push({ 218 | method: '', 219 | location: error.fileName, 220 | line: error.lineNumber, 221 | column: error.columnNumber, 222 | }); 223 | } 224 | 225 | if (!prettyOptions || !prettyOptions.showColumns) { 226 | for (var i = 0, line; line = parsedStack[i]; i++) { 227 | delete line.column; 228 | } 229 | } 230 | 231 | var prettyStack = message; 232 | if (parsedStack.length > 0) { 233 | prettyStack = prettyStack + '\n' + pretty(parsedStack, prettyOptions); 234 | } 235 | 236 | var cleanErr = Object.create(Error.prototype); 237 | cleanErr.message = message; 238 | cleanErr.stack = prettyStack; 239 | cleanErr.parsedStack = parsedStack; 240 | 241 | return cleanErr; 242 | } 243 | 244 | scope.normalize = normalize; 245 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 246 | 247 | 248 | },{"./formatting":1,"./parsing":4}],4:[function(require,module,exports){ 249 | /** 250 | * @license 251 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 252 | * 253 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 254 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 255 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 256 | * Code distributed by Google as part of the polymer project is also subject to 257 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 258 | */ 259 | (function(scope) { 260 | 'use strict'; 261 | 262 | function parse(stack) { 263 | var rawLines = stack.split('\n'); 264 | 265 | var stackyLines = compact(rawLines.map(parseStackyLine)); 266 | if (stackyLines.length === rawLines.length) return stackyLines; 267 | 268 | var v8Lines = compact(rawLines.map(parseV8Line)); 269 | if (v8Lines.length > 0) return v8Lines; 270 | 271 | var geckoLines = compact(rawLines.map(parseGeckoLine)); 272 | if (geckoLines.length > 0) return geckoLines; 273 | 274 | throw new Error('Unknown stack format: ' + stack); 275 | } 276 | 277 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack 278 | var GECKO_LINE = /^(?:([^@]*)@)?(.*?):(\d+)(?::(\d+))?$/; 279 | 280 | function parseGeckoLine(line) { 281 | var match = line.match(GECKO_LINE); 282 | if (!match) return null; 283 | return { 284 | method: match[1] || '', 285 | location: match[2] || '', 286 | line: parseInt(match[3]) || 0, 287 | column: parseInt(match[4]) || 0, 288 | }; 289 | } 290 | 291 | // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 292 | var V8_OUTER1 = /^\s*(eval )?at (.*) \((.*)\)$/; 293 | var V8_OUTER2 = /^\s*at()() (\S+)$/; 294 | var V8_INNER = /^\(?([^\(]+):(\d+):(\d+)\)?$/; 295 | 296 | function parseV8Line(line) { 297 | var outer = line.match(V8_OUTER1) || line.match(V8_OUTER2); 298 | if (!outer) return null; 299 | var inner = outer[3].match(V8_INNER); 300 | if (!inner) return null; 301 | 302 | var method = outer[2] || ''; 303 | if (outer[1]) method = 'eval at ' + method; 304 | return { 305 | method: method, 306 | location: inner[1] || '', 307 | line: parseInt(inner[2]) || 0, 308 | column: parseInt(inner[3]) || 0, 309 | }; 310 | } 311 | 312 | // Stacky.formatting.pretty 313 | 314 | var STACKY_LINE = /^\s*(.+) at (.+):(\d+):(\d+)$/; 315 | 316 | function parseStackyLine(line) { 317 | var match = line.match(STACKY_LINE); 318 | if (!match) return null; 319 | return { 320 | method: match[1] || '', 321 | location: match[2] || '', 322 | line: parseInt(match[3]) || 0, 323 | column: parseInt(match[4]) || 0, 324 | }; 325 | } 326 | 327 | // Helpers 328 | 329 | function compact(array) { 330 | var result = []; 331 | array.forEach(function(value) { 332 | if (value) { 333 | result.push(value); 334 | } 335 | }); 336 | return result; 337 | } 338 | 339 | scope.parse = parse; 340 | scope.parseGeckoLine = parseGeckoLine; 341 | scope.parseV8Line = parseV8Line; 342 | scope.parseStackyLine = parseStackyLine; 343 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 344 | 345 | },{}]},{},[2])(2) 346 | }); -------------------------------------------------------------------------------- /example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/googlearchive/stacky/fa297bb077559544c56b4a123c05df2ff97e9fdc/example.png -------------------------------------------------------------------------------- /gulpfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var coverage = require('gulp-coverage'); 14 | var coveralls = require('gulp-coveralls'); 15 | var eventStream = require('event-stream'); 16 | var gulp = require('gulp'); 17 | var jshint = require('gulp-jshint'); 18 | var lazypipe = require('lazypipe'); 19 | var mocha = require('gulp-mocha'); 20 | var notify = require('gulp-notify'); 21 | var path = require('path'); 22 | var plumber = require('gulp-plumber'); 23 | var watch = require('gulp-watch'); 24 | 25 | var ROOT = __dirname; 26 | var LIB_ROOT = path.join(ROOT, 'lib'); 27 | var TEST_ROOT = path.join(ROOT, 'test'); 28 | 29 | gulp.task('default', ['watch']); 30 | 31 | gulp.task('watch', function() { 32 | var config = { 33 | glob: '{lib,test}/**/*.js', 34 | emitOnGlob: false, 35 | gaze: {debounceDelay: 10}, 36 | }; 37 | return watch(config, function(files) { 38 | files 39 | .pipe(plumber({errorHandler: notify.onError('<%= error.message %>')})) 40 | .pipe(jshintFlow()) 41 | .pipe(eventStream.map(testForFile)) 42 | .pipe(mocha({reporter: 'dot'})) 43 | }); 44 | }); 45 | 46 | gulp.task('test', ['test:style', 'test:unit']); 47 | 48 | gulp.task('test:style', function() { 49 | return gulp.src('{lib,test}/**/*.js').pipe(jshintFlow()); 50 | }); 51 | 52 | gulp.task('test:unit', function() { 53 | return gulp.src('test/**/*.js') 54 | .pipe(coverage.instrument({pattern: ['lib/**/*.js']})) 55 | .pipe(mocha({reporter: 'spec'})) 56 | .pipe(coverage.gather()) 57 | .pipe(coverage.format({outFile: 'coverage.html'})) 58 | .pipe(gulp.dest('.')) 59 | .pipe(coverage.enforce({ 60 | statements: 95, 61 | blocks: 80, // web support drops us :( 62 | lines: 95, 63 | })); 64 | }); 65 | 66 | // Flows 67 | 68 | var jshintFlow = lazypipe() 69 | .pipe(jshint) 70 | .pipe(jshint.reporter, 'jshint-stylish') 71 | .pipe(jshint.reporter, 'fail'); 72 | 73 | // Transformation 74 | 75 | function testForFile(file, callback) { 76 | if (file.path.indexOf(LIB_ROOT) !== 0) return callback(null, file); 77 | 78 | var testPath = path.join(TEST_ROOT, file.path.substr(LIB_ROOT.length)); 79 | gulp.src(testPath) 80 | .on('data', callback.bind(null, null)) 81 | .on('error', callback); 82 | } 83 | -------------------------------------------------------------------------------- /lib/formatting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | (function(scope) { 12 | 'use strict'; 13 | 14 | var parse = scope.parse || require('./parsing').parse; 15 | 16 | scope.defaults = { 17 | // Methods are aligned up to this much padding. 18 | maxMethodPadding: 40, 19 | // A string to prefix each line with. 20 | indent: '', 21 | // A string to show for stack lines that are missing a method. 22 | methodPlaceholder: '', 23 | // A list of Strings/RegExps that will be stripped from `location` values on 24 | // each line (via `String#replace`). 25 | locationStrip: [], 26 | // A list of Strings/RegExps that indicate that a line is *not* important, and 27 | // should be styled as such. 28 | unimportantLocation: [], 29 | // A filter function to completely remove lines 30 | filter: function() { return false; }, 31 | // styles are functions that take a string and return that string when styled. 32 | styles: { 33 | method: passthrough, 34 | location: passthrough, 35 | line: passthrough, 36 | column: passthrough, 37 | unimportant: passthrough, 38 | }, 39 | }; 40 | 41 | // See Tero Tolonen's answer at 42 | // http://stackoverflow.com/questions/17575790/environment-detection-node-js-or-browser 43 | /*jshint -W054 */ 44 | var isNode = new Function('try {return this===global;}catch(e){return false;}'); 45 | 46 | // For Stacky-in-Node, we default to colored stacks. 47 | if (isNode()) { 48 | var chalk = require('chalk'); 49 | 50 | scope.defaults.styles = { 51 | method: chalk.magenta, 52 | location: chalk.blue, 53 | line: chalk.cyan, 54 | column: chalk.cyan, 55 | unimportant: chalk.dim 56 | }; 57 | } 58 | 59 | 60 | function pretty(stackOrParsed, options) { 61 | options = mergeDefaults(options || {}, scope.defaults); 62 | var lines = Array.isArray(stackOrParsed) ? stackOrParsed : parse(stackOrParsed); 63 | lines = clean(lines, options); 64 | 65 | var padSize = methodPadding(lines, options); 66 | var parts = lines.map(function(line) { 67 | var method = line.method || options.methodPlaceholder; 68 | var pad = options.indent + padding(padSize - method.length); 69 | 70 | var locationBits = [ 71 | options.styles.location(line.location), 72 | options.styles.line(line.line), 73 | ]; 74 | if ('column' in line) { 75 | locationBits.push(options.styles.column(line.column)); 76 | } 77 | var location = locationBits.join(':'); 78 | 79 | var text = pad + options.styles.method(method) + ' at ' + location; 80 | if (!line.important) { 81 | text = options.styles.unimportant(text); 82 | } 83 | return text; 84 | }); 85 | 86 | return parts.join('\n'); 87 | } 88 | 89 | function clean(lines, options) { 90 | var result = []; 91 | for (var i = 0, line; line = lines[i]; i++) { 92 | if (options.filter(line)) continue; 93 | line.location = cleanLocation(line.location, options); 94 | line.important = isImportant(line, options); 95 | result.push(line); 96 | } 97 | 98 | return result; 99 | } 100 | 101 | // Utility 102 | 103 | function passthrough(string) { 104 | return string; 105 | } 106 | 107 | function mergeDefaults(options, defaults) { 108 | var result = Object.create(defaults); 109 | Object.keys(options).forEach(function(key) { 110 | var value = options[key]; 111 | if (typeof value === 'object' && !Array.isArray(value)) { 112 | value = mergeDefaults(value, defaults[key]); 113 | } 114 | result[key] = value; 115 | }); 116 | return result; 117 | } 118 | 119 | function methodPadding(lines, options) { 120 | var size = options.methodPlaceholder.length; 121 | for (var i = 0, line; line = lines[i]; i++) { 122 | size = Math.min(options.maxMethodPadding, Math.max(size, line.method.length)); 123 | } 124 | return size; 125 | } 126 | 127 | function padding(length) { 128 | var result = ''; 129 | for (var i = 0; i < length; i++) { 130 | result = result + ' '; 131 | } 132 | return result; 133 | } 134 | 135 | function cleanLocation(location, options) { 136 | if (options.locationStrip) { 137 | for (var i = 0, matcher; matcher = options.locationStrip[i]; i++) { 138 | location = location.replace(matcher, ''); 139 | } 140 | } 141 | 142 | return location; 143 | } 144 | 145 | function isImportant(line, options) { 146 | if (options.unimportantLocation) { 147 | for (var i = 0, matcher; matcher = options.unimportantLocation[i]; i++) { 148 | if (line.location.match(matcher)) return false; 149 | } 150 | } 151 | 152 | return true; 153 | } 154 | 155 | scope.clean = clean; 156 | scope.pretty = pretty; 157 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 158 | 159 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var formatting = require('./formatting'); 14 | var normalization = require('./normalization'); 15 | var parsing = require('./parsing'); 16 | 17 | module.exports = { 18 | // Shorthands for your convenience. 19 | normalize: normalization.normalize, 20 | parse: parsing.parse, 21 | pretty: formatting.pretty, 22 | // Or the full modules. 23 | formatting: formatting, 24 | normalization: normalization, 25 | parsing: parsing, 26 | }; 27 | -------------------------------------------------------------------------------- /lib/normalization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | (function(scope) { 12 | 'use strict'; 13 | 14 | var parse = scope.parse || require('./parsing').parse; 15 | var pretty = scope.pretty || require('./formatting').pretty; 16 | 17 | function normalize(error, prettyOptions) { 18 | if (error.parsedStack) return error; 19 | var message = error.message || error.description || error || ''; 20 | var parsedStack = []; 21 | try { 22 | parsedStack = parse(error.stack || error.toString()); 23 | } catch (error) { 24 | // Ah well. 25 | } 26 | 27 | if (parsedStack.length === 0 && error.fileName) { 28 | parsedStack.push({ 29 | method: '', 30 | location: error.fileName, 31 | line: error.lineNumber, 32 | column: error.columnNumber, 33 | }); 34 | } 35 | 36 | if (!prettyOptions || !prettyOptions.showColumns) { 37 | for (var i = 0, line; line = parsedStack[i]; i++) { 38 | delete line.column; 39 | } 40 | } 41 | 42 | var prettyStack = message; 43 | if (parsedStack.length > 0) { 44 | prettyStack = prettyStack + '\n' + pretty(parsedStack, prettyOptions); 45 | } 46 | 47 | var cleanErr = Object.create(Error.prototype); 48 | cleanErr.message = message; 49 | cleanErr.stack = prettyStack; 50 | cleanErr.parsedStack = parsedStack; 51 | 52 | return cleanErr; 53 | } 54 | 55 | scope.normalize = normalize; 56 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 57 | 58 | -------------------------------------------------------------------------------- /lib/parsing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | (function(scope) { 12 | 'use strict'; 13 | 14 | function parse(stack) { 15 | var rawLines = stack.split('\n'); 16 | 17 | var stackyLines = compact(rawLines.map(parseStackyLine)); 18 | if (stackyLines.length === rawLines.length) return stackyLines; 19 | 20 | var v8Lines = compact(rawLines.map(parseV8Line)); 21 | if (v8Lines.length > 0) return v8Lines; 22 | 23 | var geckoLines = compact(rawLines.map(parseGeckoLine)); 24 | if (geckoLines.length > 0) return geckoLines; 25 | 26 | throw new Error('Unknown stack format: ' + stack); 27 | } 28 | 29 | // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error/Stack 30 | var GECKO_LINE = /^(?:([^@]*)@)?(.*?):(\d+)(?::(\d+))?$/; 31 | 32 | function parseGeckoLine(line) { 33 | var match = line.match(GECKO_LINE); 34 | if (!match) return null; 35 | return { 36 | method: match[1] || '', 37 | location: match[2] || '', 38 | line: parseInt(match[3]) || 0, 39 | column: parseInt(match[4]) || 0, 40 | }; 41 | } 42 | 43 | // https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi 44 | var V8_OUTER1 = /^\s*(eval )?at (.*) \((.*)\)$/; 45 | var V8_OUTER2 = /^\s*at()() (\S+)$/; 46 | var V8_INNER = /^\(?([^\(]+):(\d+):(\d+)\)?$/; 47 | 48 | function parseV8Line(line) { 49 | var outer = line.match(V8_OUTER1) || line.match(V8_OUTER2); 50 | if (!outer) return null; 51 | var inner = outer[3].match(V8_INNER); 52 | if (!inner) return null; 53 | 54 | var method = outer[2] || ''; 55 | if (outer[1]) method = 'eval at ' + method; 56 | return { 57 | method: method, 58 | location: inner[1] || '', 59 | line: parseInt(inner[2]) || 0, 60 | column: parseInt(inner[3]) || 0, 61 | }; 62 | } 63 | 64 | // Stacky.formatting.pretty 65 | 66 | var STACKY_LINE = /^\s*(.+) at (.+):(\d+):(\d+)$/; 67 | 68 | function parseStackyLine(line) { 69 | var match = line.match(STACKY_LINE); 70 | if (!match) return null; 71 | return { 72 | method: match[1] || '', 73 | location: match[2] || '', 74 | line: parseInt(match[3]) || 0, 75 | column: parseInt(match[4]) || 0, 76 | }; 77 | } 78 | 79 | // Helpers 80 | 81 | function compact(array) { 82 | var result = []; 83 | array.forEach(function(value) { 84 | if (value) { 85 | result.push(value); 86 | } 87 | }); 88 | return result; 89 | } 90 | 91 | scope.parse = parse; 92 | scope.parseGeckoLine = parseGeckoLine; 93 | scope.parseV8Line = parseV8Line; 94 | scope.parseStackyLine = parseStackyLine; 95 | })(typeof module !== 'undefined' ? module.exports : (this.Stacky = this.Stacky || {})); 96 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "stacky", 3 | "version": "1.3.2", 4 | "description": "Stacky parses stack traces from various sources, and formats them in readable ways.", 5 | "main": "lib", 6 | "browser": "browser.js", 7 | "files": [ 8 | "lib/", 9 | "browser.js" 10 | ], 11 | "directories": { 12 | "test": "test" 13 | }, 14 | "scripts": { 15 | "test": "gulp test", 16 | "build": "browserify --exclude chalk --standalone Stacky lib/index.js -o browser.js" 17 | }, 18 | "repository": { 19 | "type": "git", 20 | "url": "https://github.com/PolymerLabs/stacky" 21 | }, 22 | "keywords": [ 23 | "stack", 24 | "trace", 25 | "error", 26 | "stack parser", 27 | "trace parser" 28 | ], 29 | "author": "The Polymer Authors", 30 | "license": "BSD-3-Clause", 31 | "bugs": { 32 | "url": "https://github.com/PolymerLabs/stacky/issues" 33 | }, 34 | "homepage": "https://github.com/PolymerLabs/stacky", 35 | "devDependencies": { 36 | "browserify": "^12.0.1", 37 | "chai": "^3.4.1", 38 | "event-stream": "^3.1.7", 39 | "gulp": "^3.8.7", 40 | "gulp-coverage": "^0.3.38", 41 | "gulp-coveralls": "^0.1.2", 42 | "gulp-jshint": "^1.8.4", 43 | "gulp-mocha": "^2.2.0", 44 | "gulp-notify": "^2.2.0", 45 | "gulp-plumber": "^1.0.1", 46 | "gulp-watch": "^4.3.5", 47 | "jshint-stylish": "^2.1.0", 48 | "lazypipe": "^1.0.1", 49 | "mocha": "^2.3.4" 50 | }, 51 | "dependencies": { 52 | "chalk": "^1.1.1", 53 | "lodash": "^3.0.0" 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /test/formatting.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var chalk = require('chalk'); 14 | var expect = require('chai').expect; 15 | 16 | var formatting = require('../lib/formatting'); 17 | var parse = require('../lib/parsing').parse; 18 | 19 | describe('formatting', function() { 20 | 21 | describe('.pretty', function() { 22 | var pretty = formatting.pretty; 23 | 24 | it('lines up methods', function() { 25 | expect(chalk.stripColor(pretty( 26 | 'short@bar.js:1:2\n' + 27 | 'pretty damn long@bar.js:3:4\n' + 28 | 'sorta long@bar.js:5:6' 29 | ))).to.deep.eq( 30 | ' short at bar.js:1:2\n' + 31 | 'pretty damn long at bar.js:3:4\n' + 32 | ' sorta long at bar.js:5:6' 33 | ); 34 | }); 35 | 36 | it('honors maxMethodPadding', function() { 37 | expect(chalk.stripColor(pretty( 38 | 'short@bar.js:1:2\n' + 39 | 'pretty damn long@bar.js:3:4\n' + 40 | 'sorta long@bar.js:5:6', 41 | {maxMethodPadding: 10}))).to.deep.eq( 42 | ' short at bar.js:1:2\n' + 43 | 'pretty damn long at bar.js:3:4\n' + 44 | 'sorta long at bar.js:5:6' 45 | ); 46 | }); 47 | 48 | it('honors chalk.enabled', function() { 49 | expect(pretty( 50 | 'short@bar.js:1:2\n' + 51 | 'pretty damn long@baz.js:3:4\n' + 52 | 'sorta long@bar.js:5:6', 53 | {unimportantLocation: ['baz.js']})).to.deep.eq( 54 | /* jshint ignore:start */ 55 | ' \u001b[35mshort\u001b[39m at \u001b[34mbar.js\u001b[39m:\u001b[36m1\u001b[39m:\u001b[36m2\u001b[39m\n' + 56 | '\u001b[2m\u001b[35mpretty damn long\u001b[39m at \u001b[34mbaz.js\u001b[39m:\u001b[36m3\u001b[39m:\u001b[36m4\u001b[39m\u001b[22m\n' + 57 | ' \u001b[35msorta long\u001b[39m at \u001b[34mbar.js\u001b[39m:\u001b[36m5\u001b[39m:\u001b[36m6\u001b[39m' 58 | /* jshint ignore:end */ 59 | ); 60 | }); 61 | 62 | it('replaces missing methods', function() { 63 | expect(chalk.stripColor(pretty( 64 | '@bar.js:1:2\n' + 65 | 'thing@bar.js:3:4\n', 66 | {maxMethodPadding: 10}))).to.deep.eq( 67 | ' at bar.js:1:2\n' + 68 | ' thing at bar.js:3:4' 69 | ); 70 | }); 71 | 72 | }); 73 | 74 | describe('.clean', function() { 75 | var clean = formatting.clean; 76 | 77 | var options = { 78 | locationStrip: [/^bar\//, 'two/'], 79 | unimportantLocation: [/^thing/], 80 | filter: function() { return false; }, 81 | }; 82 | 83 | function cleaned(stack, key) { 84 | return clean(parse(stack), options).map(function (line) { return line[key]; }); 85 | } 86 | 87 | it('honors locationStrip', function() { 88 | var locations = cleaned('foo@bar/baz.js:1:2\nzero@one/two/three.js:4:5', 'location'); 89 | expect(locations).to.deep.eq(['baz.js', 'one/three.js']); 90 | }); 91 | 92 | it('avoids mangling locations that do not match locationStrip', function() { 93 | var locations = cleaned('foo@fizz/buzz.js:1:2\nzero@three.js:4:5', 'location'); 94 | expect(locations).to.deep.eq(['fizz/buzz.js', 'three.js']); 95 | }); 96 | 97 | it('marks unimportant lines as such', function() { 98 | var flags = cleaned('foo@bar/baz.js:1:2\n@thing/stuff.js:4:5', 'important'); 99 | expect(flags).to.deep.eq([true, false]); 100 | }); 101 | 102 | }); 103 | 104 | }); 105 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var expect = require('chai').expect; 14 | 15 | var stacky = require('../lib'); 16 | 17 | describe('stacky', function() { 18 | it('exposes .parse', function() { 19 | expect(stacky.parse).to.be.a('function'); 20 | }); 21 | 22 | it('exposes .pretty', function() { 23 | expect(stacky.pretty).to.be.a('function'); 24 | }); 25 | }); 26 | -------------------------------------------------------------------------------- /test/normalization.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var chalk = require('chalk'); 14 | var expect = require('chai').expect; 15 | 16 | var normalization = require('../lib/normalization'); 17 | 18 | var FULL_ERROR = { 19 | message: 'ReferenceError: FAIL is not defined', 20 | stack: 'ReferenceError: FAIL is not defined\n' + 21 | ' at Constraint.execute (deltablue.js:525:2)\n' + 22 | ' at Constraint.recalculate (deltablue.js:424:21)', 23 | }; 24 | 25 | describe('normalization', function() { 26 | 27 | describe('.normalize', function() { 28 | var normalize = normalization.normalize; 29 | 30 | it('hides columns by default', function() { 31 | var err = normalize(FULL_ERROR); 32 | err.stack = chalk.stripColor(err.stack); 33 | expect(err).to.deep.equal({ 34 | message: 'ReferenceError: FAIL is not defined', 35 | stack: 'ReferenceError: FAIL is not defined\n' + 36 | ' Constraint.execute at deltablue.js:525\n' + 37 | 'Constraint.recalculate at deltablue.js:424', 38 | parsedStack: [ 39 | { 40 | location: 'deltablue.js', 41 | method: 'Constraint.execute', 42 | line: 525, 43 | important: true, 44 | }, 45 | { 46 | location: 'deltablue.js', 47 | method: 'Constraint.recalculate', 48 | line: 424, 49 | important: true, 50 | }, 51 | ], 52 | }); 53 | }); 54 | 55 | it('supports modern errors', function() { 56 | var err = normalize(FULL_ERROR, {showColumns: true}); 57 | err.stack = chalk.stripColor(err.stack); 58 | expect(err).to.deep.equal({ 59 | message: 'ReferenceError: FAIL is not defined', 60 | stack: 'ReferenceError: FAIL is not defined\n' + 61 | ' Constraint.execute at deltablue.js:525:2\n' + 62 | 'Constraint.recalculate at deltablue.js:424:21', 63 | parsedStack: [ 64 | { 65 | location: 'deltablue.js', 66 | method: 'Constraint.execute', 67 | column: 2, 68 | line: 525, 69 | important: true, 70 | }, 71 | { 72 | location: 'deltablue.js', 73 | method: 'Constraint.recalculate', 74 | column: 21, 75 | line: 424, 76 | important: true, 77 | }, 78 | ], 79 | }); 80 | }); 81 | 82 | it('handles stackless errors', function() { 83 | var error = { 84 | description: 'Something brokeded', 85 | fileName: 'some/file.js', 86 | lineNumber: 123, 87 | columnNumber: 27, 88 | }; 89 | 90 | var err = normalize(error, {showColumns: true}); 91 | err.stack = chalk.stripColor(err.stack); 92 | 93 | expect(err).to.deep.equal({ 94 | message: 'Something brokeded', 95 | stack: 'Something brokeded\n' + 96 | ' at some/file.js:123:27', 97 | parsedStack: [{ 98 | location: 'some/file.js', 99 | method: '', 100 | column: 27, 101 | line: 123, 102 | important: true, 103 | }], 104 | }); 105 | }); 106 | 107 | it('handles string-only errors', function() { 108 | expect(normalize('Uncaught Error: stuff')).to.deep.equal({ 109 | message: 'Uncaught Error: stuff', 110 | stack: 'Uncaught Error: stuff', 111 | parsedStack: [], 112 | }); 113 | }); 114 | 115 | }); 116 | 117 | }); 118 | -------------------------------------------------------------------------------- /test/parsing.js: -------------------------------------------------------------------------------- 1 | /** 2 | * @license 3 | * Copyright (c) 2014 The Polymer Project Authors. All rights reserved. 4 | * 5 | * This code may only be used under the BSD style license found at polymer.github.io/LICENSE.txt 6 | * The complete set of authors may be found at polymer.github.io/AUTHORS.txt 7 | * The complete set of contributors may be found at polymer.github.io/CONTRIBUTORS.txt 8 | * Code distributed by Google as part of the polymer project is also subject to 9 | * an additional IP rights grant found at polymer.github.io/PATENTS.txt 10 | */ 11 | 'use strict'; 12 | 13 | var expect = require('chai').expect; 14 | 15 | var parsing = require('../lib/parsing'); 16 | 17 | describe('parsing', function() { 18 | 19 | describe('.parse', function() { 20 | var parse = parsing.parse; 21 | 22 | it('throws for gibberish', function() { 23 | expect(parse.bind(null, '')).to.throw(Error); 24 | expect(parse.bind(null, 'asldfkjalsdfj')).to.throw(Error); 25 | expect(parse.bind(null, 'asfdasf\n\n\n\n')).to.throw(Error); 26 | }); 27 | 28 | it('parses V8 stacks', function() { 29 | var stack = parse( 30 | 'ReferenceError: FAIL is not defined\n' + 31 | ' at Constraint.execute (deltablue.js:525:2)\n' + 32 | ' at Constraint.recalculate (deltablue.js:424:21)\n' + 33 | ' at Planner.addPropagate (deltablue.js:701:6)\n' + 34 | ' at Constraint.satisfy (deltablue.js:184:15)\n' + 35 | ' at Planner.incrementalAdd (deltablue.js:591:21)\n' + 36 | ' at Constraint.addConstraint (deltablue.js:162:10)\n' + 37 | ' at Constraint.BinaryConstraint (deltablue.js:346:7)\n' + 38 | ' at Constraint.EqualityConstraint (deltablue.js:515:38)\n' + 39 | ' at chainTest (deltablue.js:807:6)\n' + 40 | ' at deltaBlue (deltablue.js:879:2)'); 41 | expect(stack.length).to.be.eq(10); 42 | }); 43 | 44 | it('parses Gecko stacks', function() { 45 | var stack = parse( 46 | 'trace@file:///C:/example.html:9:17\n' + 47 | 'b@file:///C:/example.html:16:13\n' + 48 | 'a@file:///C:/example.html:19:13\n' + 49 | '@file:///C:/example.html:21:9'); 50 | expect(stack.length).to.be.eq(4); 51 | }); 52 | 53 | }); 54 | 55 | describe('.parseGeckoLine', function() { 56 | var parseGeckoLine = parsing.parseGeckoLine; 57 | 58 | it('parses lines w/ methods', function() { 59 | expect(parseGeckoLine('foo@bar:1:2')).to.deep.equal({ 60 | method: 'foo', 61 | location: 'bar', 62 | line: 1, 63 | column: 2, 64 | }); 65 | }); 66 | 67 | it('parses lines w/ blank methods', function() { 68 | expect(parseGeckoLine('@bar:1:2')).to.deep.equal({ 69 | method: '', 70 | location: 'bar', 71 | line: 1, 72 | column: 2, 73 | }); 74 | }); 75 | 76 | it('parses lines w/o methods', function() { 77 | expect(parseGeckoLine('bar:1:2')).to.deep.equal({ 78 | method: '', 79 | location: 'bar', 80 | line: 1, 81 | column: 2, 82 | }); 83 | }); 84 | 85 | it('parses lines w/o columns', function() { 86 | expect(parseGeckoLine('foo@bar:1')).to.deep.equal({ 87 | method: 'foo', 88 | location: 'bar', 89 | line: 1, 90 | column: 0, 91 | }); 92 | }); 93 | 94 | it('parses lines w/ URI locations', function() { 95 | expect(parseGeckoLine('assert@http://localhost:123/chai/chai.js:925:67')).to.deep.equal({ 96 | method: 'assert', 97 | location: 'http://localhost:123/chai/chai.js', 98 | line: 925, 99 | column: 67, 100 | }); 101 | }); 102 | 103 | it('parses legacy gecko lines w/ arguments', function() { 104 | expect(parseGeckoLine('b(3,4,"\n",[object])@file:///C:/example.html:16')).to.deep.equal({ 105 | method: 'b(3,4,"\n",[object])', 106 | location: 'file:///C:/example.html', 107 | line: 16, 108 | column: 0, 109 | }); 110 | }); 111 | 112 | it('returns null for gibberish', function() { 113 | expect(parseGeckoLine('jkahsdflkjhasdflkhjasf')).to.be.null; 114 | expect(parseGeckoLine('')).to.be.null; 115 | expect(parseGeckoLine(' ')).to.be.null; 116 | }); 117 | 118 | }); 119 | 120 | describe('.parseV8Line', function() { 121 | var parseV8Line = parsing.parseV8Line; 122 | 123 | it('parses minimal lines', function() { 124 | expect(parseV8Line(' at Type.name (file:1:2)')).to.deep.equal({ 125 | method: 'Type.name', 126 | location: 'file', 127 | line: 1, 128 | column: 2, 129 | }); 130 | }); 131 | 132 | it('parses without indent', function() { 133 | expect(parseV8Line('at Type.name (file:1:2)')).to.deep.equal({ 134 | method: 'Type.name', 135 | location: 'file', 136 | line: 1, 137 | column: 2, 138 | }); 139 | }); 140 | 141 | it('parses constructor calls', function() { 142 | expect(parseV8Line(' at new Foo (file:1:2)')).to.deep.equal({ 143 | method: 'new Foo', 144 | location: 'file', 145 | line: 1, 146 | column: 2, 147 | }); 148 | }); 149 | 150 | it('parses aliased method calls', function() { 151 | expect(parseV8Line(' at Type.functionName [as methodName] (file:1:2)')).to.deep.equal({ 152 | method: 'Type.functionName [as methodName]', 153 | location: 'file', 154 | line: 1, 155 | column: 2, 156 | }); 157 | }); 158 | 159 | it('parses location-only lines', function() { 160 | expect(parseV8Line(' at file:1:2')).to.deep.equal({ 161 | method: '', 162 | location: 'file', 163 | line: 1, 164 | column: 2, 165 | }); 166 | }); 167 | 168 | it('handles evals somewhat gracefully', function() { 169 | expect(parseV8Line('eval at Foo.a (eval at Bar.z (myscript.js:10:3))')).to.deep.equal({ 170 | method: 'eval at Foo.a (eval at Bar.z', 171 | location: 'myscript.js', 172 | line: 10, 173 | column: 3, 174 | }); 175 | }); 176 | 177 | it('returns null for gibberish', function() { 178 | expect(parseV8Line(' jkahsdflkjh ;as at (dflk:hja:sf)')).to.be.null; 179 | expect(parseV8Line('')).to.be.null; 180 | expect(parseV8Line(' ')).to.be.null; 181 | }); 182 | }); 183 | 184 | 185 | describe('.parseStackyLine', function() { 186 | var parseStackyLine = parsing.parseStackyLine; 187 | 188 | it('parses minimal lines', function() { 189 | expect(parseStackyLine(' Type.name at file:1:2')).to.deep.equal({ 190 | method: 'Type.name', 191 | location: 'file', 192 | line: 1, 193 | column: 2, 194 | }); 195 | }); 196 | 197 | it('parses without indent', function() { 198 | expect(parseStackyLine('Type.name at file:1:2')).to.deep.equal({ 199 | method: 'Type.name', 200 | location: 'file', 201 | line: 1, 202 | column: 2, 203 | }); 204 | }); 205 | 206 | it('parses aligned lines', function() { 207 | expect(parseStackyLine(' Type.name at file:1:2')).to.deep.equal({ 208 | method: 'Type.name', 209 | location: 'file', 210 | line: 1, 211 | column: 2, 212 | }); 213 | }); 214 | 215 | it('parses constructor calls', function() { 216 | expect(parseStackyLine(' new Foo at file:1:2')).to.deep.equal({ 217 | method: 'new Foo', 218 | location: 'file', 219 | line: 1, 220 | column: 2, 221 | }); 222 | }); 223 | }); 224 | 225 | }); 226 | --------------------------------------------------------------------------------