projecte_ionic/node_modules/karma-coverage/lib/preprocessor.js
2022-02-09 18:30:03 +01:00

202 lines
6.4 KiB
JavaScript
Executable file

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