ParsedError.coffee 5.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232
  1. sysPath = require 'path'
  2. module.exports = class ParsedError
  3. constructor: (@error) ->
  4. do @_parse
  5. _parse: ->
  6. @_trace = []
  7. @_kind = 'Error'
  8. @_wrapper = ''
  9. @_wrapper = String @error.wrapper if @error.wrapper?
  10. unless typeof @error is 'object'
  11. @_message = String @error
  12. else
  13. @_stack = @error.stack
  14. if @error.kind?
  15. @_kind = String @error.kind
  16. else if typeof @_stack is 'string'
  17. if m = @_stack.match /^([a-zA-Z0-9\_\$]+):\ /
  18. @_kind = m[1]
  19. @_message = @error.message? and String(@error.message) or ''
  20. if typeof @_stack is 'string'
  21. @_parseStack()
  22. return
  23. _parseStack: ->
  24. messageLines = []
  25. reachedTrace = no
  26. for line in @_stack.split '\n'
  27. continue if line.trim() is ''
  28. if reachedTrace
  29. @_trace.push @_parseTraceItem line
  30. else
  31. if line.match /^\s*at\s.+/
  32. reachedTrace = yes
  33. @_trace.push @_parseTraceItem line
  34. else if !@_message.split '\n'.indexOf line
  35. messageLines.push line
  36. message = messageLines.join '\n'
  37. if message.substr(0, @_kind.length) is @_kind
  38. message =
  39. message
  40. .substr(@_kind.length, message.length)
  41. .replace(/^\:\s+/, '')
  42. if message.length
  43. @_message = if @_message.length
  44. then [
  45. @_message
  46. message
  47. ].join '\n'
  48. else
  49. message
  50. return
  51. _parseTraceItem: (text) ->
  52. text = text.trim()
  53. return if text is ''
  54. return text unless text.match /^at\ /
  55. # remove the 'at ' part
  56. text = text.replace /^at /, ''
  57. return if text in ['Error (<anonymous>)', 'Error (<anonymous>:null:null)']
  58. original = text
  59. # the part that comes before the address
  60. what = null
  61. # address, including path to module and line/col
  62. addr = null
  63. # path to module
  64. path = null
  65. # module dir
  66. dir = null
  67. # module basename
  68. file = null
  69. # line number (if using a compiler, the line number of the module
  70. # in that compiler will be used)
  71. line = null
  72. # column, same as above
  73. col = null
  74. # if using a compiler, this will translate to the line number of
  75. # the js equivalent of that module
  76. jsLine = null
  77. # like above
  78. jsCol = null
  79. # path that doesn't include `node_module` dirs
  80. shortenedPath = null
  81. # like above
  82. shortenedAddr = null
  83. packageName = '[current]'
  84. # pick out the address
  85. if m = text.match /\(([^\)]+)\)$/
  86. addr = m[1].trim()
  87. if addr?
  88. what = text.substr 0, text.length - addr.length - 2
  89. what = what.trim()
  90. # might not have a 'what' clause
  91. unless addr?
  92. addr = text.trim()
  93. addr = @_fixPath addr
  94. remaining = addr
  95. # remove the <js> clause if the file is a compiled one
  96. if m = remaining.match /\,\ <js>:(\d+):(\d+)$/
  97. jsLine = m[1]
  98. jsCol = m[2]
  99. remaining = remaining.substr 0, remaining.length - m[0].length
  100. # the line/col part
  101. if m = remaining.match /:(\d+):(\d+)$/
  102. line = m[1]
  103. col = m[2]
  104. remaining = remaining.substr 0, remaining.length - m[0].length
  105. path = remaining
  106. # file and dir
  107. if path?
  108. file = sysPath.basename path
  109. dir = sysPath.dirname path
  110. if dir is '.' then dir = ''
  111. path = @_fixPath path
  112. file = @_fixPath file
  113. dir = @_fixPath dir
  114. if dir?
  115. d = dir.replace /[\\]{1,2}/g, '/'
  116. if m = d.match ///
  117. node_modules/([^/]+)(?!.*node_modules.*)
  118. ///
  119. packageName = m[1]
  120. unless jsLine?
  121. jsLine = line
  122. jsCol = col
  123. if path?
  124. r = @_rectifyPath path
  125. shortenedPath = r.path
  126. shortenedAddr = shortenedPath + addr.substr(path.length, addr.length)
  127. packages = r.packages
  128. original: original
  129. what: what
  130. addr: addr
  131. path: path
  132. dir: dir
  133. file: file
  134. line: parseInt line
  135. col: parseInt col
  136. jsLine: parseInt jsLine
  137. jsCol: parseInt jsCol
  138. packageName: packageName
  139. shortenedPath: shortenedPath
  140. shortenedAddr: shortenedAddr
  141. packages: packages || []
  142. _getMessage: -> @_message
  143. _getKind: -> @_kind
  144. _getWrapper: -> @_wrapper
  145. _getStack: -> @_stack
  146. _getArguments: -> @error.arguments
  147. _getType: -> @error.type
  148. _getTrace: -> @_trace
  149. _fixPath: (path) -> path.replace(///[\\]{1,2}///g, '/')
  150. _rectifyPath: (path, nameForCurrentPackage) ->
  151. path = String path
  152. remaining = path
  153. return path: path, packages: [] unless m = path.match /^(.+?)\/node_modules\/(.+)$/
  154. parts = []
  155. packages = []
  156. if typeof nameForCurrentPackage is 'string'
  157. parts.push "[#{nameForCurrentPackage}]"
  158. packages.push "[#{nameForCurrentPackage}]"
  159. else
  160. parts.push "[#{m[1].match(/([^\/]+)$/)[1]}]"
  161. packages.push m[1].match(/([^\/]+)$/)[1]
  162. rest = m[2]
  163. while m = rest.match /([^\/]+)\/node_modules\/(.+)$/
  164. parts.push "[#{m[1]}]"
  165. packages.push m[1]
  166. rest = m[2]
  167. if m = rest.match /([^\/]+)\/(.+)$/
  168. parts.push "[#{m[1]}]"
  169. packages.push m[1]
  170. rest = m[2]
  171. parts.push rest
  172. path: parts.join "/"
  173. packages: packages
  174. for prop in ['message', 'kind', 'arguments', 'type', 'stack', 'trace', 'wrapper'] then do ->
  175. methodName = '_get' + prop[0].toUpperCase() + prop.substr(1, prop.length)
  176. Object.defineProperty ParsedError::, prop,
  177. get: -> this[methodName]()