parcoursup/node_modules/nyc/index.js
lalBi94 7bc56c09b5 $
2023-03-05 13:23:23 +01:00

547 lines
16 KiB
JavaScript

'use strict'
/* global __coverage__ */
const cachingTransform = require('caching-transform')
const findCacheDir = require('find-cache-dir')
const fs = require('./lib/fs-promises')
const os = require('os')
const { debuglog, promisify } = require('util')
const glob = promisify(require('glob'))
const Hash = require('./lib/hash')
const libCoverage = require('istanbul-lib-coverage')
const libHook = require('istanbul-lib-hook')
const { ProcessInfo, ProcessDB } = require('istanbul-lib-processinfo')
const mkdirp = require('make-dir')
const Module = require('module')
const onExit = require('signal-exit')
const path = require('path')
const rimraf = promisify(require('rimraf'))
const SourceMaps = require('./lib/source-maps')
const TestExclude = require('test-exclude')
const pMap = require('p-map')
const getPackageType = require('get-package-type')
const debugLog = debuglog('nyc')
let selfCoverageHelper
/* istanbul ignore next */
if (/self-coverage/.test(__dirname)) {
selfCoverageHelper = require('../self-coverage-helper')
} else {
// Avoid additional conditional code
selfCoverageHelper = {
onExit () {}
}
}
function coverageFinder () {
var coverage = global.__coverage__
if (typeof __coverage__ === 'object') coverage = __coverage__
if (!coverage) coverage = global.__coverage__ = {}
return coverage
}
class NYC {
constructor (config) {
this.config = { ...config }
this.subprocessBin = config.subprocessBin || path.resolve(__dirname, './bin/nyc.js')
this._tempDirectory = config.tempDirectory || config.tempDir || './.nyc_output'
this._instrumenterLib = require(config.instrumenter || './lib/instrumenters/istanbul')
this._reportDir = config.reportDir || 'coverage'
this._sourceMap = typeof config.sourceMap === 'boolean' ? config.sourceMap : true
this._showProcessTree = config.showProcessTree || false
this._eagerInstantiation = config.eager || false
this.cwd = config.cwd || process.cwd()
this.reporter = [].concat(config.reporter || 'text')
this.cacheDirectory = (config.cacheDir && path.resolve(config.cacheDir)) || findCacheDir({ name: 'nyc', cwd: this.cwd })
this.cache = Boolean(this.cacheDirectory && config.cache)
this.extensions = [].concat(config.extension || [])
.concat('.js')
.map(ext => ext.toLowerCase())
.filter((item, pos, arr) => arr.indexOf(item) === pos)
this.exclude = new TestExclude({
cwd: this.cwd,
include: config.include,
exclude: config.exclude,
excludeNodeModules: config.excludeNodeModules !== false,
extension: this.extensions
})
this.sourceMaps = new SourceMaps({
cache: this.cache,
cacheDirectory: this.cacheDirectory
})
// require extensions can be provided as config in package.json.
this.require = [].concat(config.require || [])
this.transforms = this.extensions.reduce((transforms, ext) => {
transforms[ext] = this._createTransform(ext)
return transforms
}, {})
this.hookRequire = config.hookRequire
this.hookRunInContext = config.hookRunInContext
this.hookRunInThisContext = config.hookRunInThisContext
this.fakeRequire = null
this.processInfo = new ProcessInfo(Object.assign({}, config._processInfo, {
directory: path.resolve(this.tempDirectory(), 'processinfo')
}))
this.hashCache = {}
}
_createTransform (ext) {
const opts = {
salt: Hash.salt(this.config),
hashData: (input, metadata) => [metadata.filename],
filenamePrefix: metadata => path.parse(metadata.filename).name + '-',
onHash: (input, metadata, hash) => {
this.hashCache[metadata.filename] = hash
},
cacheDir: this.cacheDirectory,
// when running --all we should not load source-file from
// cache, we want to instead return the fake source.
disableCache: this._disableCachingTransform(),
ext: ext
}
if (this._eagerInstantiation) {
opts.transform = this._transformFactory(this.cacheDirectory)
} else {
opts.factory = this._transformFactory.bind(this)
}
return cachingTransform(opts)
}
_disableCachingTransform () {
return !(this.cache && this.config.isChildProcess)
}
_loadAdditionalModules () {
if (!this.config.useSpawnWrap || this.require.length === 0) {
return
}
const resolveFrom = require('resolve-from')
this.require.forEach(requireModule => {
// Attempt to require the module relative to the directory being instrumented.
// Then try other locations, e.g. the nyc node_modules folder.
require(resolveFrom.silent(this.cwd, requireModule) || requireModule)
})
}
instrumenter () {
return this._instrumenter || (this._instrumenter = this._createInstrumenter())
}
_createInstrumenter () {
return this._instrumenterLib({
ignoreClassMethods: [].concat(this.config.ignoreClassMethod).filter(a => a),
produceSourceMap: this.config.produceSourceMap,
compact: this.config.compact,
preserveComments: this.config.preserveComments,
esModules: this.config.esModules,
parserPlugins: this.config.parserPlugins
})
}
addFile (filename) {
const source = this._readTranspiledSource(filename)
this._maybeInstrumentSource(source, filename)
}
_readTranspiledSource (filePath) {
var source = null
var ext = path.extname(filePath)
if (typeof Module._extensions[ext] === 'undefined') {
ext = '.js'
}
Module._extensions[ext]({
_compile: function (content, filename) {
source = content
}
}, filePath)
return source
}
_getSourceMap (code, filename, hash) {
const sourceMap = {}
if (this._sourceMap) {
sourceMap.sourceMap = this.sourceMaps.extract(code, filename)
sourceMap.registerMap = () => this.sourceMaps.registerMap(filename, hash, sourceMap.sourceMap)
} else {
sourceMap.registerMap = () => {}
}
return sourceMap
}
async addAllFiles () {
this._loadAdditionalModules()
this.fakeRequire = true
const files = await this.exclude.glob(this.cwd)
for (const relFile of files) {
const filename = path.resolve(this.cwd, relFile)
const ext = path.extname(filename)
if (ext === '.mjs' || (ext === '.js' && await getPackageType(filename) === 'module')) {
const source = await fs.readFile(filename, 'utf8')
this.instrumenter().instrumentSync(
source,
filename,
this._getSourceMap(source, filename)
)
} else {
this.addFile(filename)
}
const coverage = coverageFinder()
const lastCoverage = this.instrumenter().lastFileCoverage()
if (lastCoverage) {
coverage[lastCoverage.path] = {
...lastCoverage,
// Only use this data if we don't have it without `all: true`
all: true
}
}
}
this.fakeRequire = false
this.writeCoverageFile()
}
async instrumentAllFiles (input, output) {
let inputDir = '.' + path.sep
const visitor = async relFile => {
const inFile = path.resolve(inputDir, relFile)
const inCode = await fs.readFile(inFile, 'utf-8')
const outCode = this._transform(inCode, inFile) || inCode
if (output) {
const { mode } = await fs.stat(inFile)
const outFile = path.resolve(output, relFile)
await mkdirp(path.dirname(outFile))
await fs.writeFile(outFile, outCode)
await fs.chmod(outFile, mode)
} else {
console.log(outCode)
}
}
this._loadAdditionalModules()
const stats = await fs.lstat(input)
if (stats.isDirectory()) {
inputDir = input
const filesToInstrument = await this.exclude.glob(input)
const concurrency = output ? os.cpus().length : 1
if (this.config.completeCopy && output) {
const files = await glob(path.resolve(input, '**'), {
dot: true,
nodir: true,
ignore: ['**/.git', '**/.git/**', path.join(output, '**')]
})
const destDirs = new Set(
files.map(src => path.dirname(path.join(output, path.relative(input, src))))
)
await pMap(
destDirs,
dir => mkdirp(dir),
{ concurrency }
)
await pMap(
files,
src => fs.copyFile(src, path.join(output, path.relative(input, src))),
{ concurrency }
)
}
await pMap(filesToInstrument, visitor, { concurrency })
} else {
await visitor(input)
}
}
_transform (code, filename) {
const extname = path.extname(filename).toLowerCase()
const transform = this.transforms[extname] || (() => null)
return transform(code, { filename })
}
_maybeInstrumentSource (code, filename) {
if (!this.exclude.shouldInstrument(filename)) {
return null
}
return this._transform(code, filename)
}
maybePurgeSourceMapCache () {
if (!this.cache) {
this.sourceMaps.purgeCache()
}
}
_transformFactory (cacheDir) {
const instrumenter = this.instrumenter()
let instrumented
return (code, metadata, hash) => {
const filename = metadata.filename
const sourceMap = this._getSourceMap(code, filename, hash)
try {
instrumented = instrumenter.instrumentSync(code, filename, sourceMap)
} catch (e) {
debugLog('failed to instrument ' + filename + ' with error: ' + e.stack)
if (this.config.exitOnError) {
console.error('Failed to instrument ' + filename)
process.exit(1)
} else {
instrumented = code
}
}
if (this.fakeRequire) {
return 'function x () {}'
} else {
return instrumented
}
}
}
_handleJs (code, options) {
// ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624)
const filename = path.resolve(this.cwd, options.filename)
return this._maybeInstrumentSource(code, filename) || code
}
_addHook (type) {
const handleJs = this._handleJs.bind(this)
const dummyMatcher = () => true // we do all processing in transformer
libHook['hook' + type](dummyMatcher, handleJs, { extensions: this.extensions })
}
_addRequireHooks () {
if (this.hookRequire) {
this._addHook('Require')
}
if (this.hookRunInContext) {
this._addHook('RunInContext')
}
if (this.hookRunInThisContext) {
this._addHook('RunInThisContext')
}
}
async createTempDirectory () {
await mkdirp(this.tempDirectory())
if (this.cache) {
await mkdirp(this.cacheDirectory)
}
await mkdirp(this.processInfo.directory)
}
async reset () {
if (!process.env.NYC_CWD) {
await rimraf(this.tempDirectory())
}
await this.createTempDirectory()
}
_wrapExit () {
selfCoverageHelper.registered = true
// we always want to write coverage
// regardless of how the process exits.
onExit(
() => {
this.writeCoverageFile()
selfCoverageHelper.onExit()
},
{ alwaysLast: true }
)
}
wrap (bin) {
process.env.NYC_PROCESS_ID = this.processInfo.uuid
// This is a bug with the spawn-wrap method where
// we cannot force propagation of NYC_PROCESS_ID.
if (!this.config.useSpawnWrap) {
const updateVariable = require('./lib/register-env.js')
updateVariable('NYC_PROCESS_ID')
}
this._addRequireHooks()
this._wrapExit()
this._loadAdditionalModules()
return this
}
writeCoverageFile () {
var coverage = coverageFinder()
// Remove any files that should be excluded but snuck into the coverage
Object.keys(coverage).forEach(function (absFile) {
if (!this.exclude.shouldInstrument(absFile)) {
delete coverage[absFile]
}
}, this)
if (this.cache) {
Object.keys(coverage).forEach(function (absFile) {
if (this.hashCache[absFile] && coverage[absFile]) {
coverage[absFile].contentHash = this.hashCache[absFile]
}
}, this)
}
var id = this.processInfo.uuid
var coverageFilename = path.resolve(this.tempDirectory(), id + '.json')
fs.writeFileSync(
coverageFilename,
JSON.stringify(coverage),
'utf-8'
)
this.processInfo.coverageFilename = coverageFilename
this.processInfo.files = Object.keys(coverage)
this.processInfo.saveSync()
}
async getCoverageMapFromAllCoverageFiles (baseDirectory) {
const map = libCoverage.createCoverageMap({})
const files = await this.coverageFiles(baseDirectory)
await pMap(
files,
async f => {
const report = await this.coverageFileLoad(f, baseDirectory)
map.merge(report)
},
{ concurrency: os.cpus().length }
)
map.data = await this.sourceMaps.remapCoverage(map.data)
// depending on whether source-code is pre-instrumented
// or instrumented using a JIT plugin like @babel/require
// you may opt to exclude files after applying
// source-map remapping logic.
if (this.config.excludeAfterRemap) {
map.filter(filename => this.exclude.shouldInstrument(filename))
}
return map
}
async report () {
const libReport = require('istanbul-lib-report')
const reports = require('istanbul-reports')
const context = libReport.createContext({
dir: this.reportDirectory(),
watermarks: this.config.watermarks,
coverageMap: await this.getCoverageMapFromAllCoverageFiles()
})
this.reporter.forEach((_reporter) => {
reports.create(_reporter, {
skipEmpty: this.config.skipEmpty,
skipFull: this.config.skipFull,
projectRoot: this.cwd,
maxCols: process.stdout.columns || 100
}).execute(context)
})
if (this._showProcessTree) {
await this.showProcessTree()
}
}
async writeProcessIndex () {
const db = new ProcessDB(this.processInfo.directory)
await db.writeIndex()
}
async showProcessTree () {
const db = new ProcessDB(this.processInfo.directory)
console.log(await db.renderTree(this))
}
async checkCoverage (thresholds, perFile) {
const map = await this.getCoverageMapFromAllCoverageFiles()
if (perFile) {
map.files().forEach(file => {
// ERROR: Coverage for lines (90.12%) does not meet threshold (120%) for index.js
this._checkCoverage(map.fileCoverageFor(file).toSummary(), thresholds, file)
})
} else {
// ERROR: Coverage for lines (90.12%) does not meet global threshold (120%)
this._checkCoverage(map.getCoverageSummary(), thresholds)
}
}
_checkCoverage (summary, thresholds, file) {
Object.keys(thresholds).forEach(function (key) {
var coverage = summary[key].pct
if (coverage < thresholds[key]) {
process.exitCode = 1
if (file) {
console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet threshold (' + thresholds[key] + '%) for ' + file)
} else {
console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet global threshold (' + thresholds[key] + '%)')
}
}
})
}
coverageFiles (baseDirectory = this.tempDirectory()) {
return fs.readdir(baseDirectory)
}
async coverageFileLoad (filename, baseDirectory = this.tempDirectory()) {
try {
const report = JSON.parse(await fs.readFile(path.resolve(baseDirectory, filename)), 'utf8')
await this.sourceMaps.reloadCachedSourceMaps(report)
return report
} catch (error) {
return {}
}
}
// TODO: Remove from nyc v16
async coverageData (baseDirectory) {
const files = await this.coverageFiles(baseDirectory)
return pMap(
files,
f => this.coverageFileLoad(f, baseDirectory),
{ concurrency: os.cpus().length }
)
}
tempDirectory () {
return path.resolve(this.cwd, this._tempDirectory)
}
reportDirectory () {
return path.resolve(this.cwd, this._reportDir)
}
}
module.exports = NYC