UNPKG

5.72 kBJavaScriptView Raw
1var util = require('util')
2var resolve = require('url').resolve
3var SourceMapConsumer = require('source-map').SourceMapConsumer
4var _ = require('lodash')
5
6var log = require('./logger').create('reporter')
7var MultiReporter = require('./reporters/multi')
8var baseReporterDecoratorFactory = require('./reporters/base').decoratorFactory
9
10var createErrorFormatter = function (config, emitter, SourceMapConsumer) {
11 var basePath = config.basePath
12 var urlRoot = config.urlRoot || ''
13 var lastServedFiles = []
14
15 emitter.on('file_list_modified', function (files) {
16 lastServedFiles = files.served
17 })
18
19 var findFile = function (path) {
20 for (var i = 0; i < lastServedFiles.length; i++) {
21 if (lastServedFiles[i].path === path) {
22 return lastServedFiles[i]
23 }
24 }
25 return null
26 }
27
28 var URL_REGEXP = new RegExp('(?:https?:\\/\\/' +
29 config.hostname + '(?:\\:' + config.port + ')?' + ')?\\/?' +
30 urlRoot + '\\/?' +
31 '(base/|absolute)' + // prefix, including slash for base/ to create relative paths.
32 '((?:[A-z]\\:)?[^\\?\\s\\:]*)' + // path
33 '(\\?\\w*)?' + // sha
34 '(\\:(\\d+))?' + // line
35 '(\\:(\\d+))?' + // column
36 '', 'g')
37
38 var getSourceMapConsumer = (function () {
39 var cache = new WeakMap()
40 return function (sourceMap) {
41 if (!cache.has(sourceMap)) {
42 cache.set(sourceMap, new SourceMapConsumer(sourceMap))
43 }
44 return cache.get(sourceMap)
45 }
46 }())
47
48 return function (input, indentation) {
49 indentation = _.isString(indentation) ? indentation : ''
50 if (_.isError(input)) {
51 input = input.message
52 } else if (_.isEmpty(input)) {
53 input = ''
54 } else if (!_.isString(input)) {
55 input = JSON.stringify(input, null, indentation)
56 }
57
58 // remove domain and timestamp from source files
59 // and resolve base path / absolute path urls into absolute path
60 var msg = input.replace(URL_REGEXP, function (_, prefix, path, __, ___, line, ____, column) {
61 // Find the file using basePath + path, but use the more readable path down below.
62 var file = findFile(prefix === 'base/' ? basePath + '/' + path : path)
63
64 if (file && file.sourceMap && line) {
65 line = parseInt(line || '0', 10)
66
67 column = parseInt(column, 10)
68
69 // When no column is given and we default to 0, it doesn't make sense to only search for smaller
70 // or equal columns in the sourcemap, let's search for equal or greater columns.
71 var bias = column ? SourceMapConsumer.GREATEST_LOWER_BOUND : SourceMapConsumer.LEAST_UPPER_BOUND
72
73 try {
74 var original = getSourceMapConsumer(file.sourceMap)
75 .originalPositionFor({line: line, column: (column || 0), bias: bias})
76
77 // Source maps often only have a local file name, resolve to turn into a full path if
78 // the path is not absolute yet.
79 var sourcePath = resolve(path, original.source)
80 var formattedColumn = column ? util.format(':%s', column) : ''
81 return util.format('%s:%d:%d <- %s:%d%s', sourcePath, original.line, original.column,
82 path, line, formattedColumn)
83 } catch (e) {
84 log.warn('SourceMap position not found for trace: %s', msg)
85 // Fall back to non-source-mapped formatting.
86 }
87 }
88
89 var result = path + (line ? ':' + line : '') + (column ? ':' + column : '')
90 return result || prefix
91 })
92
93 // indent every line
94 if (indentation) {
95 msg = indentation + msg.replace(/\n/g, '\n' + indentation)
96 }
97
98 // allow the user to format the error
99 if (config.formatError) {
100 return config.formatError(msg)
101 }
102
103 return msg + '\n'
104 }
105}
106
107var createReporters = function (names, config, emitter, injector) {
108 var errorFormatter = createErrorFormatter(config, emitter, SourceMapConsumer)
109 var reporters = []
110
111 // TODO(vojta): instantiate all reporters through DI
112 names.forEach(function (name) {
113 if (['dots', 'progress'].indexOf(name) !== -1) {
114 var Cls = require('./reporters/' + name)
115 var ClsColor = require('./reporters/' + name + '_color')
116 reporters.push(new Cls(errorFormatter, config.reportSlowerThan, config.colors, config.browserConsoleLogOptions))
117 return reporters.push(new ClsColor(errorFormatter, config.reportSlowerThan, config.colors, config.browserConsoleLogOptions))
118 }
119
120 var locals = {
121 baseReporterDecorator: ['factory', baseReporterDecoratorFactory],
122 formatError: ['value', errorFormatter]
123 }
124
125 try {
126 log.debug('Trying to load reporter: %s', name)
127 reporters.push(injector.createChild([locals], ['reporter:' + name]).get('reporter:' + name))
128 } catch (e) {
129 if (e.message.indexOf('No provider for "reporter:' + name + '"') !== -1) {
130 log.error('Can not load reporter "%s", it is not registered!\n ' +
131 'Perhaps you are missing some plugin?', name)
132 } else {
133 log.error('Can not load "%s"!\n ' + e.stack, name)
134 }
135 emitter.emit('load_error', 'reporter', name)
136 return
137 }
138 var colorName = name + '_color'
139 if (names.indexOf(colorName) !== -1) {
140 return
141 }
142 try {
143 log.debug('Trying to load color-version of reporter: %s (%s)', name, colorName)
144 reporters.push(injector.createChild([locals], ['reporter:' + name + '_color']).get('reporter:' + name))
145 } catch (e) {
146 log.debug('Couldn\'t load color-version.')
147 }
148 })
149
150 // bind all reporters
151 reporters.forEach(function (reporter) {
152 emitter.bind(reporter)
153 })
154
155 return new MultiReporter(reporters)
156}
157
158createReporters.$inject = [
159 'config.reporters',
160 'config',
161 'emitter',
162 'injector'
163]
164
165// PUBLISH
166exports.createReporters = createReporters