diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/Gruntfile.js b/Gruntfile.js new file mode 100644 index 0000000..69e9adb --- /dev/null +++ b/Gruntfile.js @@ -0,0 +1,20 @@ +module.exports = function(grunt) { + var pkg = require('./package.json'); + + grunt.initConfig({ + shell: { // grunt-shell-spawn + options: { + stdout: true, + stderr: true + }, + mocha: { + command: './node_modules/.bin/mocha --reporter spec test/**/*.js', + } + } + }); + + + grunt.loadNpmTasks('grunt-shell-spawn'); + + grunt.registerTask('test', ['shell:mocha']); +}; diff --git a/index.js b/index.js new file mode 100644 index 0000000..0f3bc05 --- /dev/null +++ b/index.js @@ -0,0 +1 @@ +module.exports = require('./lib/stacktrace-parser.js'); diff --git a/lib/stacktrace-parser.js b/lib/stacktrace-parser.js new file mode 100644 index 0000000..0baaf3e --- /dev/null +++ b/lib/stacktrace-parser.js @@ -0,0 +1,45 @@ + + +var UNKNOWN_FUNCTION = ''; + +var StackTraceParser = { + /** + * This parses the different stack traces and puts them into one format + * This borrows heavily from TraceKit (https://github.com/occ/TraceKit) + */ + parse: function(stackString) { + var chrome = /^\s*at (?:(?:(?:Anonymous function)?|((?:\[object object\])?\S+(?: \[as \S+\])?)) )?\(?((?:file|http|https):.*?):(\d+)(?::(\d+))?\)?\s*$/i, + gecko = /^(?:\s*(\S*)(?:\((.*?)\))?@)?((?:file|http|https).*?):(\d+)(?::(\d+))?\s*$/i, + lines = stackString.split('\n'), + stack = [], + parts, + element; + + for (var i = 0, j = lines.length; i < j; ++i) { + if ((parts = gecko.exec(lines[i]))) { + element = { + 'file': parts[3], + 'methodName': parts[1] || UNKNOWN_FUNCTION, + 'lineNumber': +parts[4], + 'column': parts[5] ? +parts[5] : null + }; + } else if ((parts = chrome.exec(lines[i]))) { + element = { + 'file': parts[2], + 'methodName': parts[1] || UNKNOWN_FUNCTION, + 'lineNumber': +parts[3], + 'column': parts[4] ? +parts[4] : null + }; + } else { + continue; + } + + stack.push(element); + } + + return stack; + } +}; + + +module.exports = StackTraceParser; diff --git a/package.json b/package.json new file mode 100644 index 0000000..fc7b4f9 --- /dev/null +++ b/package.json @@ -0,0 +1,22 @@ +{ + "author": "Georg Tavonius (http://jaz-lounge.com)", + "name": "stacktrace-parser", + "description": "Parses every stack trace into a nicely formatted array of hashes.", + "version": "0.1.0", + "engines": { + "node": "~0.10" + }, + "dependencies": {}, + "devDependencies": { + "mocha": "*", + "should": "*", + "expect.js": "*", + "grunt-cli": "~0.1.13", + "grunt": "~0.4.2", + "grunt-shell-spawn": "~0.3.0" + }, + "main": "index.js", + "scripts": { + "test": "make test" + } +} diff --git a/test/stacktrace_parser_test.js b/test/stacktrace_parser_test.js new file mode 100644 index 0000000..be06514 --- /dev/null +++ b/test/stacktrace_parser_test.js @@ -0,0 +1,220 @@ +var expect = require('expect.js'), + + StackTraceParser = require('../lib/stacktrace-parser'); + +describe('StackTraceParser', function() { + var data = { + 'Chrome & Chrome Mobile & Opera': [ + { + from: "Error: with timeout\n at http://errwischt.com/stack_traces/test:76:15\n at wrapped (http://errwischt.com/bandage.js:51:25)", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: '', + lineNumber: 76, + column: 15 }, + { file: 'http://errwischt.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 25 } + ] + }, + { + from: "Error: with timeout and named func\n at timeoutWithName (http://errwischt.com/stack_traces/test:83:15)\n at wrapped (http://errwischt.com/bandage.js:51:25)", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: 'timeoutWithName', + lineNumber: 83, + column: 15 }, + { file: 'http://errwischt.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 25 } + ] + }, + { + from: "TypeError: Object # has no method 'objectBreakDown'\n at HTMLDocument. (http://errwischt.com/stack_traces/test:91:19)\n at l (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:24882)\n at Object.c.fireWith [as resolveWith] (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:25702)\n at Function.x.extend.ready (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:2900)\n at HTMLDocument.S (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:553)", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: 'HTMLDocument.', + lineNumber: 91, + column: 19 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'l', + lineNumber: 4, + column: 24882 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'Object.c.fireWith [as resolveWith]', + lineNumber: 4, + column: 25702 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'Function.x.extend.ready', + lineNumber: 4, + column: 2900 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'HTMLDocument.S', + lineNumber: 4, + column: 553 } + ] + } + ], + 'Firefox': [ + { + from: "timeoutWithName@http://errwischt.com/stack_traces/test:83\nwrapped@http://errwischt.com/bandage.js:51", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: 'timeoutWithName', + lineNumber: 83, + column: null }, + { file: 'http://errwischt.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: null } + ] + }, + { + from: "@http://errwischt.com/stack_traces/test:76\nwrapped@http://errwischt.com/bandage.js:51", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: '', + lineNumber: 76, + column: null }, + { file: 'http://errwischt.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: null } + ] + }, + { + from: "@http://errwischt.com/stack_traces/test:97\nx.Callbacks/l@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4\nx.Callbacks/c.fireWith@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4\n.ready@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4\nS@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4", + to: [ + { file: 'http://errwischt.com/stack_traces/test', + methodName: '', + lineNumber: 97, + column: null }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'x.Callbacks/l', + lineNumber: 4, + column: null }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'x.Callbacks/c.fireWith', + lineNumber: 4, + column: null }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: '.ready', + lineNumber: 4, + column: null }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'S', + lineNumber: 4, + column: null } + ] + } + ], + 'Safari': [ + { + from: "timeoutWithName@http://bandage.local:8181/stack_traces/test:83:55\nwrapped@http://bandage.local:8181/bandage.js:51:30", + to: [ { file: 'http://bandage.local:8181/stack_traces/test', + methodName: 'timeoutWithName', + lineNumber: 83, + column: 55 }, + { file: 'http://bandage.local:8181/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 30 } ] + }, + { + from: "http://bandage.local:8181/stack_traces/test:76:40\nwrapped@http://bandage.local:8181/bandage.js:51:30", + to: [ { file: 'http://bandage.local:8181/stack_traces/test', + methodName: '', + lineNumber: 76, + column: 40 }, + { file: 'http://bandage.local:8181/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 30 } ] + }, + { + from: "http://bandage.local:8181/stack_traces/test:97:28\nl@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:24909\nfireWith@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:50440\nready@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:2933\nS@http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:580", + to: [ { file: 'http://bandage.local:8181/stack_traces/test', + methodName: '', + lineNumber: 97, + column: 28 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'l', + lineNumber: 4, + column: 24909 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'fireWith', + lineNumber: 4, + column: 50440 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'ready', + lineNumber: 4, + column: 2933 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'S', + lineNumber: 4, + column: 580 } ] + } + ], + 'Internet Explorer': [ + { + from: "Error: with timeout and named func\n at timeoutWithName (http://bandage.jaz-lounge.com/stack_traces/test:83:9)\n at wrapped (http://bandage.jaz-lounge.com/bandage.js:51:13)", + to: [ { file: 'http://bandage.jaz-lounge.com/stack_traces/test', + methodName: 'timeoutWithName', + lineNumber: 83, + column: 9 }, + { file: 'http://bandage.jaz-lounge.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 13 } ] + }, + { + from: "Error: with timeout\n at Anonymous function (http://bandage.jaz-lounge.com/stack_traces/test:76:9)\n at wrapped (http://bandage.jaz-lounge.com/bandage.js:51:13)", + to: [ { file: 'http://bandage.jaz-lounge.com/stack_traces/test', + methodName: '', + lineNumber: 76, + column: 9 }, + { file: 'http://bandage.jaz-lounge.com/bandage.js', + methodName: 'wrapped', + lineNumber: 51, + column: 13 } ] + }, + { + from: "TypeError: Object doesn't support property or method 'objectBreakDown'\n at Anonymous function (http://bandage.jaz-lounge.com/stack_traces/test:91:7)\n at l (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:24874)\n at fireWith (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:25638)\n at ready (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:2898)\n at S (http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js:4:551)", + to: [ { file: 'http://bandage.jaz-lounge.com/stack_traces/test', + methodName: '', + lineNumber: 91, + column: 7 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'l', + lineNumber: 4, + column: 24874 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'fireWith', + lineNumber: 4, + column: 25638 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'ready', + lineNumber: 4, + column: 2898 }, + { file: 'http://ajax.googleapis.com/ajax/libs/jquery/2.0.3/jquery.min.js', + methodName: 'S', + lineNumber: 4, + column: 551 } ] + } + ] + }; + + Object.keys(data).forEach(function(browser) { + describe('can parse stack trace of ' + browser, function() { + data[browser].forEach(function(browserData) { + it(browserData.from, function() { + var result = StackTraceParser.parse(browserData.from); + expect(result.length).to.equal(browserData.to.length); + expect(result).to.eql(browserData.to); + }); + }); + }); + }); +});