123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569 |
- 'use strict'
- /* global __coverage__ */
- const arrify = require('arrify')
- const cachingTransform = require('caching-transform')
- const util = require('util')
- const findCacheDir = require('find-cache-dir')
- const fs = require('fs')
- const glob = require('glob')
- const Hash = require('./lib/hash')
- const libCoverage = require('istanbul-lib-coverage')
- const libHook = require('istanbul-lib-hook')
- const libReport = require('istanbul-lib-report')
- const mkdirp = require('make-dir')
- const Module = require('module')
- const onExit = require('signal-exit')
- const path = require('path')
- const reports = require('istanbul-reports')
- const resolveFrom = require('resolve-from')
- const rimraf = require('rimraf')
- const SourceMaps = require('./lib/source-maps')
- const testExclude = require('test-exclude')
- const uuid = require('uuid/v4')
- const debugLog = util.debuglog('nyc')
- var ProcessInfo
- try {
- ProcessInfo = require('./lib/process.covered.js')
- } catch (e) {
- /* istanbul ignore next */
- ProcessInfo = require('./lib/process.js')
- }
- /* istanbul ignore next */
- if (/index\.covered\.js$/.test(__filename)) {
- require('./lib/self-coverage-helper')
- }
- function NYC (config) {
- config = 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 = arrify(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.exclude = testExclude({
- cwd: this.cwd,
- include: config.include,
- exclude: config.exclude
- })
- this.sourceMaps = new SourceMaps({
- cache: this.cache,
- cacheDirectory: this.cacheDirectory
- })
- // require extensions can be provided as config in package.json.
- this.require = arrify(config.require)
- this.extensions = arrify(config.extension).concat('.js').map(function (ext) {
- return ext.toLowerCase()
- }).filter(function (item, pos, arr) {
- // avoid duplicate extensions
- return arr.indexOf(item) === pos
- })
- this.transforms = this.extensions.reduce(function (transforms, ext) {
- transforms[ext] = this._createTransform(ext)
- return transforms
- }.bind(this), {})
- this.hookRequire = config.hookRequire
- this.hookRunInContext = config.hookRunInContext
- this.hookRunInThisContext = config.hookRunInThisContext
- this.fakeRequire = null
- this.processInfo = new ProcessInfo(config && config._processInfo)
- this.rootId = this.processInfo.root || this.generateUniqueID()
- this.hashCache = {}
- }
- NYC.prototype._createTransform = function (ext) {
- var opts = {
- salt: Hash.salt,
- hashData: (input, metadata) => [metadata.filename],
- 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)
- }
- NYC.prototype._disableCachingTransform = function () {
- return !(this.cache && this.config.isChildProcess)
- }
- NYC.prototype._loadAdditionalModules = function () {
- var _this = this
- this.require.forEach(function (r) {
- // first attempt to require the module relative to
- // the directory being instrumented.
- var p = resolveFrom.silent(_this.cwd, r)
- if (p) {
- require(p)
- return
- }
- // now try other locations, .e.g, the nyc node_modules folder.
- require(r)
- })
- }
- NYC.prototype.instrumenter = function () {
- return this._instrumenter || (this._instrumenter = this._createInstrumenter())
- }
- NYC.prototype._createInstrumenter = function () {
- return this._instrumenterLib(this.cwd, {
- ignoreClassMethods: [].concat(this.config.ignoreClassMethod).filter(a => a),
- produceSourceMap: this.config.produceSourceMap,
- compact: this.config.compact,
- preserveComments: this.config.preserveComments,
- esModules: this.config.esModules,
- plugins: this.config.plugins
- })
- }
- NYC.prototype.addFile = function (filename) {
- var relFile = path.relative(this.cwd, filename)
- var source = this._readTranspiledSource(path.resolve(this.cwd, filename))
- var instrumentedSource = this._maybeInstrumentSource(source, filename, relFile)
- return {
- instrument: !!instrumentedSource,
- relFile: relFile,
- content: instrumentedSource || source
- }
- }
- NYC.prototype._readTranspiledSource = function (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
- }
- NYC.prototype.addAllFiles = function () {
- var _this = this
- this._loadAdditionalModules()
- this.fakeRequire = true
- this.walkAllFiles(this.cwd, function (filename) {
- filename = path.resolve(_this.cwd, filename)
- if (_this.exclude.shouldInstrument(filename)) {
- _this.addFile(filename)
- var coverage = coverageFinder()
- var lastCoverage = _this.instrumenter().lastFileCoverage()
- if (lastCoverage) {
- filename = lastCoverage.path
- coverage[filename] = lastCoverage
- }
- }
- })
- this.fakeRequire = false
- this.writeCoverageFile()
- }
- NYC.prototype.instrumentAllFiles = function (input, output, cb) {
- var _this = this
- var inputDir = '.' + path.sep
- var visitor = function (filename) {
- var ext
- var transform
- var inFile = path.resolve(inputDir, filename)
- var code = fs.readFileSync(inFile, 'utf-8')
- for (ext in _this.transforms) {
- if (filename.toLowerCase().substr(-ext.length) === ext) {
- transform = _this.transforms[ext]
- break
- }
- }
- if (transform) {
- code = transform(code, { filename: filename, relFile: inFile })
- }
- if (!output) {
- console.log(code)
- } else {
- var outFile = path.resolve(output, filename)
- mkdirp.sync(path.dirname(outFile))
- fs.writeFileSync(outFile, code, 'utf-8')
- }
- }
- this._loadAdditionalModules()
- try {
- var stats = fs.lstatSync(input)
- if (stats.isDirectory()) {
- inputDir = input
- this.walkAllFiles(input, visitor)
- } else {
- visitor(input)
- }
- } catch (err) {
- return cb(err)
- }
- cb()
- }
- NYC.prototype.walkAllFiles = function (dir, visitor) {
- var pattern = null
- if (this.extensions.length === 1) {
- pattern = '**/*' + this.extensions[0]
- } else {
- pattern = '**/*{' + this.extensions.join() + '}'
- }
- glob.sync(pattern, { cwd: dir, nodir: true, ignore: this.exclude.exclude }).forEach(function (filename) {
- visitor(filename)
- })
- }
- NYC.prototype._maybeInstrumentSource = function (code, filename, relFile) {
- var instrument = this.exclude.shouldInstrument(filename, relFile)
- if (!instrument) {
- return null
- }
- var ext, transform
- for (ext in this.transforms) {
- if (filename.toLowerCase().substr(-ext.length) === ext) {
- transform = this.transforms[ext]
- break
- }
- }
- return transform ? transform(code, { filename: filename, relFile: relFile }) : null
- }
- NYC.prototype._transformFactory = function (cacheDir) {
- const instrumenter = this.instrumenter()
- let instrumented
- return (code, metadata, hash) => {
- const filename = metadata.filename
- let sourceMap = null
- if (this._sourceMap) sourceMap = this.sourceMaps.extractAndRegister(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
- }
- }
- }
- NYC.prototype._handleJs = function (code, options) {
- var filename = options.filename
- var relFile = path.relative(this.cwd, filename)
- // ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624)
- filename = path.resolve(this.cwd, relFile)
- return this._maybeInstrumentSource(code, filename, relFile) || code
- }
- NYC.prototype._addHook = function (type) {
- var handleJs = this._handleJs.bind(this)
- var dummyMatcher = function () { return true } // we do all processing in transformer
- libHook['hook' + type](dummyMatcher, handleJs, { extensions: this.extensions })
- }
- NYC.prototype._addRequireHooks = function () {
- if (this.hookRequire) {
- this._addHook('Require')
- }
- if (this.hookRunInContext) {
- this._addHook('RunInContext')
- }
- if (this.hookRunInThisContext) {
- this._addHook('RunInThisContext')
- }
- }
- NYC.prototype.cleanup = function () {
- if (!process.env.NYC_CWD) rimraf.sync(this.tempDirectory())
- }
- NYC.prototype.clearCache = function () {
- if (this.cache) {
- rimraf.sync(this.cacheDirectory)
- }
- }
- NYC.prototype.createTempDirectory = function () {
- mkdirp.sync(this.tempDirectory())
- if (this.cache) mkdirp.sync(this.cacheDirectory)
- if (this._showProcessTree) {
- mkdirp.sync(this.processInfoDirectory())
- }
- }
- NYC.prototype.reset = function () {
- this.cleanup()
- this.createTempDirectory()
- }
- NYC.prototype._wrapExit = function () {
- var _this = this
- // we always want to write coverage
- // regardless of how the process exits.
- onExit(function () {
- _this.writeCoverageFile()
- }, { alwaysLast: true })
- }
- NYC.prototype.wrap = function (bin) {
- this._addRequireHooks()
- this._wrapExit()
- this._loadAdditionalModules()
- return this
- }
- NYC.prototype.generateUniqueID = uuid
- NYC.prototype.writeCoverageFile = function () {
- var coverage = coverageFinder()
- if (!coverage) return
- // 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)
- } else {
- coverage = this.sourceMaps.remapCoverage(coverage)
- }
- var id = this.generateUniqueID()
- var coverageFilename = path.resolve(this.tempDirectory(), id + '.json')
- fs.writeFileSync(
- coverageFilename,
- JSON.stringify(coverage),
- 'utf-8'
- )
- if (!this._showProcessTree) {
- return
- }
- this.processInfo.coverageFilename = coverageFilename
- fs.writeFileSync(
- path.resolve(this.processInfoDirectory(), id + '.json'),
- JSON.stringify(this.processInfo),
- 'utf-8'
- )
- }
- function coverageFinder () {
- var coverage = global.__coverage__
- if (typeof __coverage__ === 'object') coverage = __coverage__
- if (!coverage) coverage = global['__coverage__'] = {}
- return coverage
- }
- NYC.prototype.getCoverageMapFromAllCoverageFiles = function (baseDirectory) {
- var _this = this
- var map = libCoverage.createCoverageMap({})
- this.eachReport(undefined, (report) => {
- map.merge(report)
- }, baseDirectory)
- // 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(function (filename) {
- return _this.exclude.shouldInstrument(filename)
- })
- }
- map.data = this.sourceMaps.remapCoverage(map.data)
- return map
- }
- NYC.prototype.report = function () {
- var tree
- var map = this.getCoverageMapFromAllCoverageFiles()
- var context = libReport.createContext({
- dir: this.reportDirectory(),
- watermarks: this.config.watermarks
- })
- tree = libReport.summarizers.pkg(map)
- this.reporter.forEach((_reporter) => {
- tree.visit(reports.create(_reporter, {
- skipEmpty: this.config.skipEmpty,
- skipFull: this.config.skipFull
- }), context)
- })
- if (this._showProcessTree) {
- this.showProcessTree()
- }
- }
- NYC.prototype.showProcessTree = function () {
- var processTree = ProcessInfo.buildProcessTree(this._loadProcessInfos())
- console.log(processTree.render(this))
- }
- NYC.prototype.checkCoverage = function (thresholds, perFile) {
- var map = this.getCoverageMapFromAllCoverageFiles()
- var nyc = this
- if (perFile) {
- map.files().forEach(function (file) {
- // ERROR: Coverage for lines (90.12%) does not meet threshold (120%) for index.js
- nyc._checkCoverage(map.fileCoverageFor(file).toSummary(), thresholds, file)
- })
- } else {
- // ERROR: Coverage for lines (90.12%) does not meet global threshold (120%)
- nyc._checkCoverage(map.getCoverageSummary(), thresholds)
- }
- // process.exitCode was not implemented until v0.11.8.
- if (/^v0\.(1[0-1]\.|[0-9]\.)/.test(process.version) && process.exitCode !== 0) process.exit(process.exitCode)
- }
- NYC.prototype._checkCoverage = function (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] + '%)')
- }
- }
- })
- }
- NYC.prototype._loadProcessInfos = function () {
- var _this = this
- var files = fs.readdirSync(this.processInfoDirectory())
- return files.map(function (f) {
- try {
- return new ProcessInfo(JSON.parse(fs.readFileSync(
- path.resolve(_this.processInfoDirectory(), f),
- 'utf-8'
- )))
- } catch (e) { // handle corrupt JSON output.
- return {}
- }
- })
- }
- NYC.prototype.eachReport = function (filenames, iterator, baseDirectory) {
- baseDirectory = baseDirectory || this.tempDirectory()
- if (typeof filenames === 'function') {
- iterator = filenames
- filenames = undefined
- }
- var _this = this
- var files = filenames || fs.readdirSync(baseDirectory)
- files.forEach(function (f) {
- var report
- try {
- report = JSON.parse(fs.readFileSync(
- path.resolve(baseDirectory, f),
- 'utf-8'
- ))
- _this.sourceMaps.reloadCachedSourceMaps(report)
- } catch (e) { // handle corrupt JSON output.
- report = {}
- }
- iterator(report)
- })
- }
- NYC.prototype.loadReports = function (filenames) {
- var reports = []
- this.eachReport(filenames, (report) => {
- reports.push(report)
- })
- return reports
- }
- NYC.prototype.tempDirectory = function () {
- return path.resolve(this.cwd, this._tempDirectory)
- }
- NYC.prototype.reportDirectory = function () {
- return path.resolve(this.cwd, this._reportDir)
- }
- NYC.prototype.processInfoDirectory = function () {
- return path.resolve(this.tempDirectory(), 'processinfo')
- }
- module.exports = NYC
|