| // 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 |