UNPKG

5.21 kBJavaScriptView Raw
1// Coverage Preprocessor
2// =====================
3//
4// Depends on the the reporter to generate an actual report
5
6// Dependencies
7// ------------
8
9var istanbul = require('istanbul')
10var minimatch = require('minimatch')
11var path = require('path')
12var SourceMapConsumer = require('source-map').SourceMapConsumer
13var SourceMapGenerator = require('source-map').SourceMapGenerator
14var globalSourceCache = require('./source-cache')
15var extend = require('util')._extend
16var coverageMap = require('./coverage-map')
17
18// Regexes
19// -------
20
21var coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g
22
23// Preprocessor creator function
24function createCoveragePreprocessor (logger, helper, basePath, reporters, coverageReporter) {
25 var _ = helper._
26 var log = logger.create('preprocessor.coverage')
27
28 // Options
29 // -------
30
31 var instrumenterOverrides = {}
32 var instrumenters = {istanbul: istanbul}
33 var includeAllSources = false
34 var useJSExtensionForCoffeeScript = false
35
36 if (coverageReporter) {
37 instrumenterOverrides = coverageReporter.instrumenter
38 instrumenters = extend({istanbul: istanbul}, coverageReporter.instrumenters)
39 includeAllSources = coverageReporter.includeAllSources === true
40 useJSExtensionForCoffeeScript = coverageReporter.useJSExtensionForCoffeeScript === true
41 }
42
43 var sourceCache = globalSourceCache.get(basePath)
44
45 var instrumentersOptions = _.reduce(instrumenters, function getInstumenterOptions (memo, instrument, name) {
46 memo[name] = {}
47
48 if (coverageReporter && coverageReporter.instrumenterOptions) {
49 memo[name] = coverageReporter.instrumenterOptions[name]
50 }
51
52 return memo
53 }, {})
54
55 // if coverage reporter is not used, do not preprocess the files
56 if (!_.includes(reporters, 'coverage')) {
57 return function (content, _, done) {
58 done(content)
59 }
60 }
61
62 // check instrumenter override requests
63 function checkInstrumenters () {
64 return _.reduce(instrumenterOverrides, function (acc, literal, pattern) {
65 if (!_.includes(_.keys(instrumenters), String(literal))) {
66 log.error('Unknown instrumenter: %s', literal)
67 return false
68 }
69 return acc
70 }, true)
71 }
72
73 if (!checkInstrumenters()) {
74 return function (content, _, done) {
75 return done(1)
76 }
77 }
78
79 return function (content, file, done) {
80 log.debug('Processing "%s".', file.originalPath)
81
82 var jsPath = path.resolve(file.originalPath)
83 // default instrumenters
84 var instrumenterLiteral = 'istanbul'
85
86 _.forEach(instrumenterOverrides, function (literal, pattern) {
87 if (minimatch(file.originalPath, pattern, {dot: true})) {
88 instrumenterLiteral = String(literal)
89 }
90 })
91
92 var InstrumenterConstructor = instrumenters[instrumenterLiteral].Instrumenter
93 var constructOptions = instrumentersOptions[instrumenterLiteral] || {}
94 var codeGenerationOptions = null
95
96 if (file.sourceMap) {
97 log.debug('Enabling source map generation for "%s".', file.originalPath)
98 codeGenerationOptions = extend({
99 format: {
100 compact: !constructOptions.noCompact
101 },
102 sourceMap: file.sourceMap.file,
103 sourceMapWithCode: true,
104 file: file.path
105 }, constructOptions.codeGenerationOptions || {})
106 }
107
108 var options = extend({}, constructOptions)
109 options = extend(options, {codeGenerationOptions: codeGenerationOptions})
110
111 var instrumenter = new InstrumenterConstructor(options)
112 instrumenter.instrument(content, jsPath, function (err, instrumentedCode) {
113 if (err) {
114 log.error('%s\n at %s', err.message, file.originalPath)
115 done(err.message)
116 } else {
117 if (file.sourceMap && instrumenter.lastSourceMap()) {
118 log.debug('Adding source map to instrumented file for "%s".', file.originalPath)
119 var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(instrumenter.lastSourceMap().toString()))
120 generator.applySourceMap(new SourceMapConsumer(file.sourceMap))
121 file.sourceMap = JSON.parse(generator.toString())
122 instrumentedCode += '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
123 instrumentedCode += new Buffer(JSON.stringify(file.sourceMap)).toString('base64') + '\n'
124 }
125
126 // remember the actual immediate instrumented JS for given original path
127 sourceCache[jsPath] = content
128
129 if (includeAllSources) {
130 // reset stateful regex
131 coverageObjRegex.lastIndex = 0
132
133 var coverageObjMatch = coverageObjRegex.exec(instrumentedCode)
134
135 if (coverageObjMatch !== null) {
136 var coverageObj = JSON.parse(coverageObjMatch[0])
137
138 coverageMap.add(coverageObj)
139 }
140 }
141
142 // RequireJS expects JavaScript files to end with `.js`
143 if (useJSExtensionForCoffeeScript && instrumenterLiteral === 'ibrik') {
144 file.path = file.path.replace(/\.coffee$/, '.js')
145 }
146
147 done(instrumentedCode)
148 }
149 })
150 }
151}
152
153createCoveragePreprocessor.$inject = [
154 'logger',
155 'helper',
156 'config.basePath',
157 'config.reporters',
158 'config.coverageReporter'
159]
160
161module.exports = createCoveragePreprocessor