You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

202 lines
8.5 KiB

4 years ago
  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 sanitizedLine = line.replace(/^\s+/, '').replace(/\(eval code/g, '(');
  55. // capture and preseve the parenthesized location "(/foo/my bar.js:12:87)" in
  56. // case it has spaces in it, as the string is split on \s+ later on
  57. var location = sanitizedLine.match(/ (\((.+):(\d+):(\d+)\)$)/);
  58. // remove the parenthesized location from the line, if it was matched
  59. sanitizedLine = location ? sanitizedLine.replace(location[0], '') : sanitizedLine;
  60. var tokens = sanitizedLine.split(/\s+/).slice(1);
  61. // if a location was matched, pass it to extractLocation() otherwise pop the last token
  62. var locationParts = this.extractLocation(location ? location[1] : tokens.pop());
  63. var functionName = tokens.join(' ') || undefined;
  64. var fileName = ['eval', '<anonymous>'].indexOf(locationParts[0]) > -1 ? undefined : locationParts[0];
  65. return new StackFrame({
  66. functionName: functionName,
  67. fileName: fileName,
  68. lineNumber: locationParts[1],
  69. columnNumber: locationParts[2],
  70. source: line
  71. });
  72. }, this);
  73. },
  74. parseFFOrSafari: function ErrorStackParser$$parseFFOrSafari(error) {
  75. var filtered = error.stack.split('\n').filter(function(line) {
  76. return !line.match(SAFARI_NATIVE_CODE_REGEXP);
  77. }, this);
  78. return filtered.map(function(line) {
  79. // Throw away eval information until we implement stacktrace.js/stackframe#8
  80. if (line.indexOf(' > eval') > -1) {
  81. line = line.replace(/ line (\d+)(?: > eval line \d+)* > eval\:\d+\:\d+/g, ':$1');
  82. }
  83. if (line.indexOf('@') === -1 && line.indexOf(':') === -1) {
  84. // Safari eval frames only have function names and nothing else
  85. return new StackFrame({
  86. functionName: line
  87. });
  88. } else {
  89. var functionNameRegex = /((.*".+"[^@]*)?[^@]*)(?:@)/;
  90. var matches = line.match(functionNameRegex);
  91. var functionName = matches && matches[1] ? matches[1] : undefined;
  92. var locationParts = this.extractLocation(line.replace(functionNameRegex, ''));
  93. return new StackFrame({
  94. functionName: functionName,
  95. fileName: locationParts[0],
  96. lineNumber: locationParts[1],
  97. columnNumber: locationParts[2],
  98. source: line
  99. });
  100. }
  101. }, this);
  102. },
  103. parseOpera: function ErrorStackParser$$parseOpera(e) {
  104. if (!e.stacktrace || (e.message.indexOf('\n') > -1 &&
  105. e.message.split('\n').length > e.stacktrace.split('\n').length)) {
  106. return this.parseOpera9(e);
  107. } else if (!e.stack) {
  108. return this.parseOpera10(e);
  109. } else {
  110. return this.parseOpera11(e);
  111. }
  112. },
  113. parseOpera9: function ErrorStackParser$$parseOpera9(e) {
  114. var lineRE = /Line (\d+).*script (?:in )?(\S+)/i;
  115. var lines = e.message.split('\n');
  116. var result = [];
  117. for (var i = 2, len = lines.length; i < len; i += 2) {
  118. var match = lineRE.exec(lines[i]);
  119. if (match) {
  120. result.push(new StackFrame({
  121. fileName: match[2],
  122. lineNumber: match[1],
  123. source: lines[i]
  124. }));
  125. }
  126. }
  127. return result;
  128. },
  129. parseOpera10: function ErrorStackParser$$parseOpera10(e) {
  130. var lineRE = /Line (\d+).*script (?:in )?(\S+)(?:: In function (\S+))?$/i;
  131. var lines = e.stacktrace.split('\n');
  132. var result = [];
  133. for (var i = 0, len = lines.length; i < len; i += 2) {
  134. var match = lineRE.exec(lines[i]);
  135. if (match) {
  136. result.push(
  137. new StackFrame({
  138. functionName: match[3] || undefined,
  139. fileName: match[2],
  140. lineNumber: match[1],
  141. source: lines[i]
  142. })
  143. );
  144. }
  145. }
  146. return result;
  147. },
  148. // Opera 10.65+ Error.stack very similar to FF/Safari
  149. parseOpera11: function ErrorStackParser$$parseOpera11(error) {
  150. var filtered = error.stack.split('\n').filter(function(line) {
  151. return !!line.match(FIREFOX_SAFARI_STACK_REGEXP) && !line.match(/^Error created at/);
  152. }, this);
  153. return filtered.map(function(line) {
  154. var tokens = line.split('@');
  155. var locationParts = this.extractLocation(tokens.pop());
  156. var functionCall = (tokens.shift() || '');
  157. var functionName = functionCall
  158. .replace(/<anonymous function(: (\w+))?>/, '$2')
  159. .replace(/\([^\)]*\)/g, '') || undefined;
  160. var argsRaw;
  161. if (functionCall.match(/\(([^\)]*)\)/)) {
  162. argsRaw = functionCall.replace(/^[^\(]+\(([^\)]*)\)$/, '$1');
  163. }
  164. var args = (argsRaw === undefined || argsRaw === '[arguments not available]') ?
  165. undefined : argsRaw.split(',');
  166. return new StackFrame({
  167. functionName: functionName,
  168. args: args,
  169. fileName: locationParts[0],
  170. lineNumber: locationParts[1],
  171. columnNumber: locationParts[2],
  172. source: line
  173. });
  174. }, this);
  175. }
  176. };
  177. }));