error-stack-parser.js 7.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192
  1. (function(root, factory) {
  2. 'use strict';
  3. // Universal Module Definition (UMD) to support AMD, CommonJS/Node.js, Rhino, and browsers.
  4. /* istanbul ignore next */
  5. if (typeof define === 'function' && define.amd) {
  6. define('error-stack-parser', ['stackframe'], factory);
  7. } else if (typeof exports === 'object') {
  8. module.exports = factory(require('stackframe'));
  9. } else {
  10. root.ErrorStackParser = factory(root.StackFrame);
  11. }
  12. }(this, function ErrorStackParser(StackFrame) {
  13. 'use strict';
  14. var FIREFOX_SAFARI_STACK_REGEXP = /(^|@)\S+\:\d+/;
  15. var CHROME_IE_STACK_REGEXP = /^\s*at .*(\S+\:\d+|\(native\))/m;
  16. var SAFARI_NATIVE_CODE_REGEXP = /^(eval@)?(\[native code\])?$/;
  17. return {
  18. /**
  19. * Given an Error object, extract the most information from it.
  20. *
  21. * @param {Error} error object
  22. * @return {Array} of StackFrames
  23. */
  24. parse: function ErrorStackParser$$parse(error) {
  25. if (typeof error.stacktrace !== 'undefined' || typeof error['opera#sourceloc'] !== 'undefined') {
  26. return this.parseOpera(error);
  27. } else if (error.stack && error.stack.match(CHROME_IE_STACK_REGEXP)) {
  28. return this.parseV8OrIE(error);
  29. } else if (error.stack) {
  30. return this.parseFFOrSafari(error);
  31. } else {
  32. throw new Error('Cannot parse given Error object');
  33. }
  34. },
  35. // Separate line and column numbers from a string of the form: (URI:Line:Column)
  36. extractLocation: function ErrorStackParser$$extractLocation(urlLike) {
  37. // Fail-fast but return locations like "(native)"
  38. if (urlLike.indexOf(':') === -1) {
  39. return [urlLike];
  40. }
  41. var regExp = /(.+?)(?:\:(\d+))?(?:\:(\d+))?$/;
  42. var parts = regExp.exec(urlLike.replace(/[\(\)]/g, ''));
  43. return [parts[1], parts[2] || undefined, parts[3] || undefined];
  44. },
  45. parseV8OrIE: function ErrorStackParser$$parseV8OrIE(error) {
  46. var filtered = error.stack.split('\n').filter(function(line) {
  47. return !!line.match(CHROME_IE_STACK_REGEXP);
  48. }, this);
  49. return filtered.map(function(line) {
  50. if (line.indexOf('(eval ') > -1) {
  51. // Throw away eval information until we implement stacktrace.js/stackframe#8
  52. line = line.replace(/eval code/g, 'eval').replace(/(\(eval at [^\()]*)|(\)\,.*$)/g, '');
  53. }
  54. var tokens = line.replace(/^\s+/, '').replace(/\(eval code/g, '(').split(/\s+/).slice(1);
  55. var locationParts = this.extractLocation(tokens.pop());
  56. var functionName = tokens.join(' ') || undefined;
  57. var fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];
  58. return new StackFrame({
  59. functionName: functionName,
  60. fileName: fileName,
  61. lineNumber: locationParts[1],
  62. columnNumber: locationParts[2],
  63. source: line
  64. });
  65. }, this);
  66. },
  67. parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) {
  68. var filtered = error.stack.split('\n').filter(function(line) {
  69. return !line.match(SAFARI_NATIVE_CODE_REGEXP);
  70. }, this);
  71. return filtered.map(function(line) {
  72. // Throw away eval information until we implement stacktrace.js/stackframe#8
  73. if (line.indexOf(' > eval') > -1) {
  74. line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g, ':$1');
  75. }
  76. if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {
  77. // Safari eval frames only have function names and nothing else
  78. return new StackFrame({
  79. functionName: line
  80. });
  81. } else {
  82. var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
  83. var matches = line.match(functionNameRegex);
  84. var functionName = matches && matches[1] ? matches[1] : undefined;
  85. var locationParts = this.extractLocation(line.replace(functionNameRegex, ''));
  86. return new StackFrame({
  87. functionName: functionName,
  88. fileName: locationParts[0],
  89. lineNumber: locationParts[1],
  90. columnNumber: locationParts[2],
  91. source: line
  92. });
  93. }
  94. }, this);
  95. },
  96. parseOpera: function ErrorStackParser$$parseOpera(e) {
  97. if (!e.stacktrace || (e.message.indexOf('\n') > -1 &&
  98. e.message.split('\n').length > e.stacktrace.split('\n').length)) {
  99. return this.parseOpera9(e);
  100. } else if (!e.stack) {
  101. return this.parseOpera10(e);
  102. } else {
  103. return this.parseOpera11(e);
  104. }
  105. },
  106. parseOpera9: function ErrorStackParser$$parseOpera9(e) {
  107. var lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
  108. var lines = e.message.split('\n');
  109. var result = [];
  110. for (var i = 2, len = lines.length; i < len; i += 2) {
  111. var match = lineRE.exec(lines[i]);
  112. if (match) {
  113. result.push(new StackFrame({
  114. fileName: match[2],
  115. lineNumber: match[1],
  116. source: lines[i]
  117. }));
  118. }
  119. }
  120. return result;
  121. },
  122. parseOpera10: function ErrorStackParser$$parseOpera10(e) {
  123. var lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
  124. var lines = e.stacktrace.split('\n');
  125. var result = [];
  126. for (var i = 0, len = lines.length; i < len; i += 2) {
  127. var match = lineRE.exec(lines[i]);
  128. if (match) {
  129. result.push(
  130. new StackFrame({
  131. functionName: match[3] || undefined,
  132. fileName: match[2],
  133. lineNumber: match[1],
  134. source: lines[i]
  135. })
  136. );
  137. }
  138. }
  139. return result;
  140. },
  141. // Opera 10.65+ Error.stack very similar to FF/Safari
  142. parseOpera11: function ErrorStackParser$$parseOpera11(error) {
  143. var filtered = error.stack.split('\n').filter(function(line) {
  144. return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
  145. }, this);
  146. return filtered.map(function(line) {
  147. var tokens = line.split('@');
  148. var locationParts = this.extractLocation(tokens.pop());
  149. var functionCall = (tokens.shift() || '');
  150. var functionName = functionCall
  151. .replace(/<anonymous function(: (\w+))?>/, '$2')
  152. .replace(/\([^\)]*\)/g, '') || undefined;
  153. var argsRaw;
  154. if (functionCall.match(/\(([^\)]*)\)/)) {
  155. argsRaw = functionCall.replace(/^[^\(]+\(([^\)]*)\)$/, '$1');
  156. }
  157. var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ?
  158. undefined : argsRaw.split(',');
  159. return new StackFrame({
  160. functionName: functionName,
  161. args: args,
  162. fileName: locationParts[0],
  163. lineNumber: locationParts[1],
  164. columnNumber: locationParts[2],
  165. source: line
  166. });
  167. }, this);
  168. }
  169. };
  170. }));