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