reporter.js 4.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141
  1. 'use strict'
  2. const resolve = require('url').resolve
  3. const SourceMapConsumer = require('source-map').SourceMapConsumer
  4. const _ = require('lodash')
  5. const PathUtils = require('./utils/path-utils')
  6. const log = require('./logger').create('reporter')
  7. const MultiReporter = require('./reporters/multi')
  8. const baseReporterDecoratorFactory = require('./reporters/base').decoratorFactory
  9. function createErrorFormatter (config, emitter, SourceMapConsumer) {
  10. const basePath = config.basePath
  11. const urlRoot = config.urlRoot === '/' ? '' : (config.urlRoot || '')
  12. let lastServedFiles = []
  13. emitter.on('file_list_modified', (files) => {
  14. lastServedFiles = files.served
  15. })
  16. const URL_REGEXP = new RegExp('(?:https?:\\/\\/' +
  17. config.hostname + '(?:\\:' + config.port + ')?' + ')?\\/?' +
  18. urlRoot + '\\/?' +
  19. '(base/|absolute)' + // prefix, including slash for base/ to create relative paths.
  20. '((?:[A-z]\\:)?[^\\?\\s\\:]*)' + // path
  21. '(\\?\\w*)?' + // sha
  22. '(\\:(\\d+))?' + // line
  23. '(\\:(\\d+))?' + // column
  24. '', 'g')
  25. const cache = new WeakMap()
  26. function getSourceMapConsumer (sourceMap) {
  27. if (!cache.has(sourceMap)) {
  28. cache.set(sourceMap, new SourceMapConsumer(sourceMap))
  29. }
  30. return cache.get(sourceMap)
  31. }
  32. return function (input, indentation) {
  33. indentation = _.isString(indentation) ? indentation : ''
  34. if (_.isError(input)) {
  35. input = input.message
  36. } else if (_.isEmpty(input)) {
  37. input = ''
  38. } else if (!_.isString(input)) {
  39. input = JSON.stringify(input, null, indentation)
  40. }
  41. var msg = input.replace(URL_REGEXP, function (_, prefix, path, __, ___, line, ____, column) {
  42. const normalizedPath = prefix === 'base/' ? `${basePath}/${path}` : path
  43. const file = lastServedFiles.find((file) => file.path === normalizedPath)
  44. if (file && file.sourceMap && line) {
  45. line = +line
  46. column = +column
  47. // When no column is given and we default to 0, it doesn't make sense to only search for smaller
  48. // or equal columns in the sourcemap, let's search for equal or greater columns.
  49. const bias = column ? SourceMapConsumer.GREATEST_LOWER_BOUND : SourceMapConsumer.LEAST_UPPER_BOUND
  50. try {
  51. const original = getSourceMapConsumer(file.sourceMap).originalPositionFor({ line, column: (column || 0), bias })
  52. // Source maps often only have a local file name, resolve to turn into a full path if
  53. // the path is not absolute yet.
  54. return `${PathUtils.formatPathMapping(resolve(path, original.source), original.line, original.column)} <- ${PathUtils.formatPathMapping(path, line, column)}`
  55. } catch (e) {
  56. log.warn(`SourceMap position not found for trace: ${input}`)
  57. }
  58. }
  59. return PathUtils.formatPathMapping(path, line, column) || prefix
  60. })
  61. if (indentation) {
  62. msg = indentation + msg.replace(/\n/g, '\n' + indentation)
  63. }
  64. return config.formatError ? config.formatError(msg) : msg + '\n'
  65. }
  66. }
  67. function createReporters (names, config, emitter, injector) {
  68. const errorFormatter = createErrorFormatter(config, emitter, SourceMapConsumer)
  69. const reporters = []
  70. names.forEach((name) => {
  71. if (['dots', 'progress'].indexOf(name) !== -1) {
  72. [
  73. require('./reporters/' + name),
  74. require('./reporters/' + name + '_color')
  75. ].forEach((Reporter) => {
  76. reporters.push(new Reporter(errorFormatter, config.reportSlowerThan, config.colors, config.browserConsoleLogOptions))
  77. })
  78. return
  79. }
  80. const locals = {
  81. baseReporterDecorator: ['factory', baseReporterDecoratorFactory],
  82. formatError: ['value', errorFormatter]
  83. }
  84. try {
  85. log.debug('Trying to load reporter: %s', name)
  86. reporters.push(injector.createChild([locals], ['reporter:' + name]).get('reporter:' + name))
  87. } catch (e) {
  88. if (e.message.indexOf(`No provider for "reporter:${name}"`) !== -1) {
  89. log.error(`Can not load reporter "${name}", it is not registered!\n Perhaps you are missing some plugin?`)
  90. } else {
  91. log.error(`Can not load "${name}"!\n ${e.stack}`)
  92. }
  93. emitter.emit('load_error', 'reporter', name)
  94. return
  95. }
  96. const colorName = name + '_color'
  97. if (names.indexOf(colorName) === -1) {
  98. try {
  99. log.debug(`Trying to load color-version of reporter: ${name} (${colorName})`)
  100. reporters.push(injector.createChild([locals], ['reporter:' + colorName]).get('reporter:' + name))
  101. } catch (e) {
  102. log.debug('Couldn\'t load color-version.')
  103. }
  104. }
  105. })
  106. reporters.forEach((reporter) => emitter.bind(reporter))
  107. return new MultiReporter(reporters)
  108. }
  109. createReporters.$inject = [
  110. 'config.reporters',
  111. 'config',
  112. 'emitter',
  113. 'injector'
  114. ]
  115. exports.createReporters = createReporters