blob: 995dbb433914bc89be0fc64703cad1080ff5d2a3 [file] [log] [blame]
// Coverage Preprocessor
// =====================
//
// Depends on the the reporter to generate an actual report
// Dependencies
// ------------
var istanbul = require('istanbul')
var minimatch = require('minimatch')
var path = require('path')
var _ = require('lodash')
var SourceMapConsumer = require('source-map').SourceMapConsumer
var SourceMapGenerator = require('source-map').SourceMapGenerator
var globalSourceCache = require('./source-cache')
var extend = require('util')._extend
var coverageMap = require('./coverage-map')
// Regexes
// -------
var coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g
// Preprocessor creator function
function createCoveragePreprocessor (logger, helper, basePath, reporters, coverageReporter) {
var log = logger.create('preprocessor.coverage')
// Options
// -------
var instrumenterOverrides = {}
var instrumenters = {istanbul: istanbul}
var includeAllSources = false
var useJSExtensionForCoffeeScript = false
if (coverageReporter) {
instrumenterOverrides = coverageReporter.instrumenter
instrumenters = extend({istanbul: istanbul}, coverageReporter.instrumenters)
includeAllSources = coverageReporter.includeAllSources === true
useJSExtensionForCoffeeScript = coverageReporter.useJSExtensionForCoffeeScript === true
}
var sourceCache = globalSourceCache.get(basePath)
var instrumentersOptions = _.reduce(instrumenters, function getInstumenterOptions (memo, instrument, name) {
memo[name] = {}
if (coverageReporter && coverageReporter.instrumenterOptions) {
memo[name] = coverageReporter.instrumenterOptions[name]
}
return memo
}, {})
// if coverage reporter is not used, do not preprocess the files
if (!_.includes(reporters, 'coverage')) {
return function (content, _, done) {
done(content)
}
}
// check instrumenter override requests
function checkInstrumenters () {
return _.reduce(instrumenterOverrides, function (acc, literal, pattern) {
if (!_.includes(_.keys(instrumenters), String(literal))) {
log.error('Unknown instrumenter: %s', literal)
return false
}
return acc
}, true)
}
if (!checkInstrumenters()) {
return function (content, _, done) {
return done(1)
}
}
return function (content, file, done) {
log.debug('Processing "%s".', file.originalPath)
var jsPath = path.resolve(file.originalPath)
// default instrumenters
var instrumenterLiteral = 'istanbul'
_.forEach(instrumenterOverrides, function (literal, pattern) {
if (minimatch(file.originalPath, pattern, {dot: true})) {
instrumenterLiteral = String(literal)
}
})
var InstrumenterConstructor = instrumenters[instrumenterLiteral].Instrumenter
var constructOptions = instrumentersOptions[instrumenterLiteral] || {}
var codeGenerationOptions = null
if (file.sourceMap) {
log.debug('Enabling source map generation for "%s".', file.originalPath)
codeGenerationOptions = extend({
format: {
compact: !constructOptions.noCompact
},
sourceMap: file.sourceMap.file,
sourceMapWithCode: true,
file: file.path
}, constructOptions.codeGenerationOptions || {})
}
var options = extend({}, constructOptions)
options = extend(options, {codeGenerationOptions: codeGenerationOptions})
var instrumenter = new InstrumenterConstructor(options)
instrumenter.instrument(content, jsPath, function (err, instrumentedCode) {
if (err) {
log.error('%s\n at %s', err.message, file.originalPath)
done(err.message)
} else {
if (file.sourceMap && instrumenter.lastSourceMap()) {
log.debug('Adding source map to instrumented file for "%s".', file.originalPath)
var generator = SourceMapGenerator.fromSourceMap(new SourceMapConsumer(instrumenter.lastSourceMap().toString()))
generator.applySourceMap(new SourceMapConsumer(file.sourceMap))
file.sourceMap = JSON.parse(generator.toString())
instrumentedCode += '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
instrumentedCode += new Buffer(JSON.stringify(file.sourceMap)).toString('base64') + '\n'
}
// remember the actual immediate instrumented JS for given original path
sourceCache[jsPath] = content
if (includeAllSources) {
// reset stateful regex
coverageObjRegex.lastIndex = 0
var coverageObjMatch = coverageObjRegex.exec(instrumentedCode)
if (coverageObjMatch !== null) {
var coverageObj = JSON.parse(coverageObjMatch[0])
coverageMap.add(coverageObj)
}
}
// RequireJS expects JavaScript files to end with `.js`
if (useJSExtensionForCoffeeScript && instrumenterLiteral === 'ibrik') {
file.path = file.path.replace(/\.coffee$/, '.js')
}
done(instrumentedCode)
}
})
}
}
createCoveragePreprocessor.$inject = [
'logger',
'helper',
'config.basePath',
'config.reporters',
'config.coverageReporter'
]
module.exports = createCoveragePreprocessor