ParsedError.coffee 5.1 KB

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