text: // Coverage Preprocessor
  // =====================
  //
  // Depends on the the reporter to generate an actual report
  
  // Dependencies
  // ------------
  
  const { createInstrumenter } = require('istanbul-lib-instrument')
  const minimatch = require('minimatch')
  const path = require('path')
  const globalSourceMapStore = require('./source-map-store')
  const globalCoverageMap = require('./coverage-map')
  
  // Regexes
  // -------
  
  const coverageObjRegex = /\{.*"path".*"fnMap".*"statementMap".*"branchMap".*\}/g
  
  // Preprocessor creator function
  function createCoveragePreprocessor (logger, basePath, reporters = [], coverageReporter = {}) {
    const log = logger.create('preprocessor.coverage')
  
    // Options
    // -------
  
    function isConstructor (Func) {
      try {
        // eslint-disable-next-line
        new Func()
      } catch (err) {
        // error message should be of the form: "TypeError: func is not a constructor"
        // test for this type of message to ensure we failed due to the function not being
        // constructable
        if (/TypeError.*constructor/.test(err.message)) {
          return false
        }
      }
      return true
    }
  
    function getCreatorFunction (Obj) {
      if (Obj.Instrumenter) {
        return function (opts) {
          return new Obj.Instrumenter(opts)
        }
      }
      if (typeof Obj !== 'function') {
        // Object doesn't have old instrumenter variable and isn't a
        // constructor, so we can't use it to create an instrumenter
        return null
      }
      if (isConstructor(Obj)) {
        return function (opts) {
          return new Obj(opts)
        }
      }
      return Obj
    }
  
    const instrumenters = { istanbul: createInstrumenter }
    const instrumenterOverrides = coverageReporter.instrumenter || {}
    const { includeAllSources = false, useJSExtensionForCoffeeScript = false } = coverageReporter
  
    Object.entries(coverageReporter.instrumenters || {}).forEach(([literal, instrumenter]) => {
      const creatorFunction = getCreatorFunction(instrumenter)
      if (creatorFunction) {
        instrumenters[literal] = creatorFunction
      }
    })
  
    const sourceMapStore = globalSourceMapStore.get(basePath)
  
    const instrumentersOptions = Object.keys(instrumenters).reduce((memo, key) => {
      memo[key] = {}
  
      if (coverageReporter.instrumenterOptions) {
        memo[key] = coverageReporter.instrumenterOptions[key]
      }
  
      return memo
    }, {})
  
    // if coverage reporter is not used, do not preprocess the files
    if (!reporters.includes('coverage')) {
      log.info('coverage not included in reporters %s', reporters)
      return function (content, _, done) {
        done(content)
      }
    }
    log.debug('coverage included in reporters %s', reporters)
  
    // check instrumenter override requests
    function checkInstrumenters () {
      const keys = Object.keys(instrumenters)
      return Object.values(instrumenterOverrides).some(literal => {
        const notIncluded = !keys.includes(String(literal))
        if (notIncluded) {
          log.error('Unknown instrumenter: %s', literal)
        }
        return notIncluded
      })
    }
  
    if (checkInstrumenters()) {
      return function (content, _, done) {
        return done(1)
      }
    }
  
    return function (content, file, done) {
      log.debug('Processing "%s".', file.originalPath)
  
      const jsPath = path.resolve(file.originalPath)
      // 'istanbul' is default instrumenters
      const instrumenterLiteral = Object.keys(instrumenterOverrides).reduce((res, pattern) => {
        if (minimatch(file.originalPath, pattern, { dot: true })) {
          return instrumenterOverrides[pattern]
        }
        return res
      }, 'istanbul')
  
      const instrumenterCreator = instrumenters[instrumenterLiteral]
      const constructOptions = instrumentersOptions[instrumenterLiteral] || {}
      let options = Object.assign({}, constructOptions)
      let codeGenerationOptions = null
      options.autoWrap = options.autoWrap || !options.noAutoWrap
  
      if (file.sourceMap) {
        log.debug('Enabling source map generation for "%s".', file.originalPath)
        codeGenerationOptions = Object.assign({}, {
          format: {
            compact: !constructOptions.noCompact
          },
          sourceMap: file.sourceMap.file,
          sourceMapWithCode: true,
          file: file.path
        }, constructOptions.codeGenerationOptions || {})
        options.produceSourceMap = true
      }
  
      options = Object.assign({}, options, { codeGenerationOptions: codeGenerationOptions })
  
      const instrumenter = instrumenterCreator(options)
      instrumenter.instrument(content, jsPath, function (err, instrumentedCode) {
        if (err) {
          log.error('%s\n  at %s', err.message, file.originalPath)
          done(err.message)
        } else {
          // Register the incoming sourceMap for transformation during reporting (if it exists)
          if (file.sourceMap) {
            sourceMapStore.registerMap(jsPath, file.sourceMap)
          }
  
          // Add merged source map (if it merged correctly)
          const lastSourceMap = instrumenter.lastSourceMap()
          if (lastSourceMap) {
            log.debug('Adding source map to instrumented file for "%s".', file.originalPath)
            file.sourceMap = lastSourceMap
            instrumentedCode += '\n//# sourceMappingURL=data:application/json;charset=utf-8;base64,'
            instrumentedCode += Buffer.from(JSON.stringify(lastSourceMap)).toString('base64') + '\n'
          }
  
          if (includeAllSources) {
            let coverageObj
            // Check if the file coverage object is exposed from the instrumenter directly
            if (instrumenter.lastFileCoverage) {
              coverageObj = instrumenter.lastFileCoverage()
              globalCoverageMap.add(coverageObj)
            } else {
              // Attempt to match and parse coverage object from instrumented code
  
              // reset stateful regex
              coverageObjRegex.lastIndex = 0
              const coverageObjMatch = coverageObjRegex.exec(instrumentedCode)
              if (coverageObjMatch !== null) {
                coverageObj = JSON.parse(coverageObjMatch[0])
                globalCoverageMap.add(coverageObj)
              }
            }
          }
  
          // RequireJS expects JavaScript files to end with `.js`
          if (useJSExtensionForCoffeeScript && instrumenterLiteral === 'ibrik') {
            file.path = file.path.replace(/\.coffee$/, '.js')
          }
  
          done(instrumentedCode)
        }
      }, file.sourceMap)
    }
  }
  
  createCoveragePreprocessor.$inject = [
    'logger',
    'config.basePath',
    'config.reporters',
    'config.coverageReporter'
  ]
  
  module.exports = createCoveragePreprocessor
  