PrettyError.coffee 7.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346
  1. isPlainObject = require 'lodash/isPlainObject'
  2. defaultStyle = require './defaultStyle'
  3. ParsedError = require './ParsedError'
  4. nodePaths = require './nodePaths'
  5. RenderKid = require 'renderkid'
  6. merge = require 'lodash/merge'
  7. arrayUtils =
  8. pluckByCallback: (a, cb) ->
  9. return a if a.length < 1
  10. removed = 0
  11. for value, index in a
  12. if cb value, index
  13. removed++
  14. continue
  15. if removed isnt 0
  16. a[index - removed] = a[index]
  17. if removed > 0
  18. a.length = a.length - removed
  19. a
  20. pluckOneItem: (a, item) ->
  21. return a if a.length < 1
  22. reached = no
  23. for value, index in a
  24. if not reached
  25. if value is item
  26. reached = yes
  27. continue
  28. else
  29. a[index - 1] = a[index]
  30. a.length = a.length - 1 if reached
  31. a
  32. instance = null
  33. module.exports = class PrettyError
  34. self = @
  35. @_filters:
  36. 'module.exports': (item) ->
  37. return unless item.what?
  38. item.what = item.what.replace /\.module\.exports\./g, ' - '
  39. return
  40. @_getDefaultStyle: ->
  41. defaultStyle()
  42. @start: ->
  43. unless instance?
  44. instance = new self
  45. instance.start()
  46. instance
  47. @stop: ->
  48. instance?.stop()
  49. constructor: ->
  50. @_useColors = yes
  51. @_maxItems = 50
  52. @_packagesToSkip = []
  53. @_pathsToSkip = []
  54. @_skipCallbacks = []
  55. @_filterCallbacks = []
  56. @_parsedErrorFilters = []
  57. @_aliases = []
  58. @_renderer = new RenderKid
  59. @_style = self._getDefaultStyle()
  60. @_renderer.style @_style
  61. start: ->
  62. @_oldPrepareStackTrace = Error.prepareStackTrace
  63. prepeare = @_oldPrepareStackTrace or (exc, frames) ->
  64. result = exc.toString()
  65. frames = frames.map (frame) -> " at #{frame.toString()}"
  66. result + "\n" + frames.join "\n"
  67. # https://code.google.com/p/v8/wiki/JavaScriptStackTraceApi
  68. Error.prepareStackTrace = (exc, trace) =>
  69. stack = prepeare.apply(null, arguments)
  70. @render {stack, message: exc.toString().replace /^.*: /, ''}, no
  71. @
  72. stop: ->
  73. Error.prepareStackTrace = @_oldPrepareStackTrace
  74. @_oldPrepareStackTrace = null
  75. config: (c) ->
  76. if c.skipPackages?
  77. if c.skipPackages is no
  78. @unskipAllPackages()
  79. else
  80. @skipPackage.apply @, c.skipPackages
  81. if c.skipPaths?
  82. if c.skipPaths is no
  83. @unskipAllPaths()
  84. else
  85. @skipPath.apply @, c.skipPaths
  86. if c.skip?
  87. if c.skip is no
  88. @unskipAll()
  89. else
  90. @skip.apply @, c.skip
  91. if c.maxItems?
  92. @setMaxItems c.maxItems
  93. if c.skipNodeFiles is yes
  94. @skipNodeFiles()
  95. else if c.skipNodeFiles is no
  96. @unskipNodeFiles()
  97. if c.filters?
  98. if c.filters is no
  99. @removeAllFilters()
  100. else
  101. @filter.apply @, c.filters
  102. if c.parsedErrorFilters?
  103. if c.parsedErrorFilters is no
  104. @removeAllParsedErrorFilters()
  105. else
  106. @filterParsedError.apply @, c.parsedErrorFilters
  107. if c.aliases?
  108. if isPlainObject c.aliases
  109. @alias path, alias for path, alias of c.aliases
  110. else if c.aliases is no
  111. @removeAllAliases()
  112. @
  113. withoutColors: ->
  114. @_useColors = false
  115. @
  116. withColors: ->
  117. @_useColors = true
  118. @
  119. skipPackage: (packages...) ->
  120. @_packagesToSkip.push String pkg for pkg in packages
  121. @
  122. unskipPackage: (packages...) ->
  123. arrayUtils.pluckOneItem(@_packagesToSkip, pkg) for pkg in packages
  124. @
  125. unskipAllPackages: ->
  126. @_packagesToSkip.length = 0
  127. @
  128. skipPath: (paths...) ->
  129. @_pathsToSkip.push path for path in paths
  130. @
  131. unskipPath: (paths...) ->
  132. arrayUtils.pluckOneItem(@_pathsToSkip, path) for path in paths
  133. @
  134. unskipAllPaths: ->
  135. @_pathsToSkip.length = 0
  136. @
  137. skip: (callbacks...) ->
  138. @_skipCallbacks.push cb for cb in callbacks
  139. @
  140. unskip: (callbacks...) ->
  141. arrayUtils.pluckOneItem(@_skipCallbacks, cb) for cb in callbacks
  142. @
  143. unskipAll: ->
  144. @_skipCallbacks.length = 0
  145. @
  146. skipNodeFiles: ->
  147. @skipPath.apply @, nodePaths
  148. unskipNodeFiles: ->
  149. @unskipPath.apply @, nodePaths
  150. filter: (callbacks...) ->
  151. @_filterCallbacks.push cb for cb in callbacks
  152. @
  153. removeFilter: (callbacks...) ->
  154. arrayUtils.pluckOneItem(@_filterCallbacks, cb) for cb in callbacks
  155. @
  156. removeAllFilters: ->
  157. @_filterCallbacks.length = 0
  158. @
  159. filterParsedError: (callbacks...) ->
  160. @_parsedErrorFilters.push cb for cb in callbacks
  161. @
  162. removeParsedErrorFilter: (callbacks...) ->
  163. arrayUtils.pluckOneItem(@_parsedErrorFilters, cb) for cb in callbacks
  164. @
  165. removeAllParsedErrorFilters: ->
  166. @_parsedErrorFilters.length = 0
  167. @
  168. setMaxItems: (maxItems = 50) ->
  169. if maxItems is 0 then maxItems = 50
  170. @_maxItems = maxItems|0
  171. @
  172. alias: (stringOrRx, alias) ->
  173. @_aliases.push {stringOrRx, alias}
  174. @
  175. removeAlias: (stringOrRx) ->
  176. arrayUtils.pluckByCallback @_aliases, (pair) ->
  177. pair.stringOrRx is stringOrRx
  178. @
  179. removeAllAliases: ->
  180. @_aliases.length = 0
  181. @
  182. _getStyle: ->
  183. @_style
  184. appendStyle: (toAppend) ->
  185. merge @_style, toAppend
  186. @_renderer.style toAppend
  187. @
  188. _getRenderer: ->
  189. @_renderer
  190. render: (e, logIt = no, useColors = @_useColors) ->
  191. obj = @getObject e
  192. rendered = @_renderer.render(obj, useColors)
  193. console.error rendered if logIt is yes
  194. rendered
  195. getObject: (e) ->
  196. unless e instanceof ParsedError
  197. e = new ParsedError e
  198. @_applyParsedErrorFiltersOn e
  199. header =
  200. title: do ->
  201. ret = {}
  202. # some errors are thrown to display other errors.
  203. # we call them wrappers here.
  204. if e.wrapper isnt ''
  205. ret.wrapper = "#{e.wrapper}"
  206. ret.kind = e.kind
  207. ret
  208. colon: ':'
  209. message: String(e.message).trim()
  210. traceItems = []
  211. count = -1
  212. for item, i in e.trace
  213. continue unless item?
  214. continue if @_skipOrFilter(item, i) is yes
  215. count++
  216. break if count > @_maxItems
  217. if typeof item is 'string'
  218. traceItems.push item: custom: item
  219. continue
  220. traceItems.push do ->
  221. markupItem = item:
  222. header:
  223. pointer: do ->
  224. return '' unless item.file?
  225. file: item.file
  226. colon: ':'
  227. line: item.line
  228. footer: do ->
  229. foooter = addr: item.shortenedAddr
  230. if item.extra? then foooter.extra = item.extra
  231. foooter
  232. markupItem.item.header.what = item.what if typeof item.what is 'string' and item.what.trim().length > 0
  233. markupItem
  234. obj = 'pretty-error':
  235. header: header
  236. if traceItems.length > 0
  237. obj['pretty-error'].trace = traceItems
  238. obj
  239. _skipOrFilter: (item, itemNumber) ->
  240. if typeof item is 'object'
  241. return yes if item.modName in @_packagesToSkip
  242. return yes if item.path in @_pathsToSkip
  243. for modName in item.packages
  244. return yes if modName in @_packagesToSkip
  245. if typeof item.shortenedAddr is 'string'
  246. for pair in @_aliases
  247. item.shortenedAddr = item.shortenedAddr.replace pair.stringOrRx, pair.alias
  248. for cb in @_skipCallbacks
  249. return yes if cb(item, itemNumber) is yes
  250. for cb in @_filterCallbacks
  251. cb(item, itemNumber)
  252. return no
  253. _applyParsedErrorFiltersOn: (error) ->
  254. for cb in @_parsedErrorFilters
  255. cb error
  256. return
  257. for prop in ['renderer', 'style'] then do ->
  258. methodName = '_get' + prop[0].toUpperCase() + prop.substr(1, prop.length)
  259. PrettyError::__defineGetter__ prop, -> do @[methodName]