diff options
Diffstat (limited to 'node_modules/nyc/index.js')
-rwxr-xr-x | node_modules/nyc/index.js | 505 |
1 files changed, 505 insertions, 0 deletions
diff --git a/node_modules/nyc/index.js b/node_modules/nyc/index.js new file mode 100755 index 000000000..4c93cd3bd --- /dev/null +++ b/node_modules/nyc/index.js @@ -0,0 +1,505 @@ +/* global __coverage__ */ + +const arrify = require('arrify') +const cachingTransform = require('caching-transform') +const debugLog = require('debug-log')('nyc') +const findCacheDir = require('find-cache-dir') +const fs = require('fs') +const glob = require('glob') +const Hash = require('./lib/hash') +const js = require('default-require-extensions/js') +const libCoverage = require('istanbul-lib-coverage') +const libHook = require('istanbul-lib-hook') +const libReport = require('istanbul-lib-report') +const md5hex = require('md5-hex') +const mkdirp = require('mkdirp') +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') + +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 || './.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 || 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.hookRunInContext = config.hookRunInContext + 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 _this = this + var opts = { + salt: Hash.salt, + hash: function (code, metadata, salt) { + var hash = Hash(code, metadata.filename) + _this.hashCache[metadata.filename] = hash + return 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(_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, { + produceSourceMap: this.config.produceSourceMap + }) +} + +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) + _this.addFile(filename) + var coverage = coverageFinder() + var lastCoverage = _this.instrumenter().lastFileCoverage() + if (lastCoverage) { + filename = lastCoverage.path + } + if (lastCoverage && _this.exclude.shouldInstrument(filename)) { + 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) + } +} + +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) { + var _this = this + var instrumenter = this.instrumenter() + var instrumented + + return function (code, metadata, hash) { + var filename = metadata.filename + var sourceMap = null + + if (_this._sourceMap) sourceMap = _this.sourceMaps.extractAndRegister(code, filename, hash) + + try { + instrumented = instrumenter.instrumentSync(code, filename, sourceMap) + } catch (e) { + // don't fail external tests due to instrumentation bugs. + debugLog('failed to instrument ' + filename + 'with error: ' + e.stack) + instrumented = code + } + + if (_this.fakeRequire) { + return 'function x () {}' + } else { + return instrumented + } + } +} + +NYC.prototype._handleJs = function (code, 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._wrapRequire = function () { + this.extensions.forEach(function (ext) { + require.extensions[ext] = js + }) + this._addHook('Require') +} + +NYC.prototype._addOtherHooks = function () { + if (this.hookRunInContext) { + 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._wrapRequire() + this._addOtherHooks() + this._wrapExit() + this._loadAdditionalModules() + return this +} + +NYC.prototype.generateUniqueID = function () { + return md5hex( + process.hrtime().concat(process.pid).map(String) + ) +} + +NYC.prototype.writeCoverageFile = function () { + var coverage = coverageFinder() + if (!coverage) return + + 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 () { + var map = libCoverage.createCoverageMap({}) + + this.loadReports().forEach(function (report) { + map.merge(report) + }) + 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._reportDir, + watermarks: this.config.watermarks + }) + + tree = libReport.summarizers.pkg(map) + + this.reporter.forEach(function (_reporter) { + tree.visit(reports.create(_reporter), 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) { + var map = this._getCoverageMapFromAllCoverageFiles() + var summary = map.getCoverageSummary() + + // ERROR: Coverage for lines (90.12%) does not meet global threshold (120%) + Object.keys(thresholds).forEach(function (key) { + var coverage = summary[key].pct + if (coverage < thresholds[key]) { + process.exitCode = 1 + console.error('ERROR: Coverage for ' + key + ' (' + coverage + '%) does not meet global threshold (' + thresholds[key] + '%)') + } + }) + + // 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._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.loadReports = function (filenames) { + var _this = this + var files = filenames || fs.readdirSync(this.tempDirectory()) + + return files.map(function (f) { + var report + try { + report = JSON.parse(fs.readFileSync( + path.resolve(_this.tempDirectory(), f), + 'utf-8' + )) + } catch (e) { // handle corrupt JSON output. + return {} + } + + _this.sourceMaps.reloadCachedSourceMaps(report) + return report + }) +} + +NYC.prototype.tempDirectory = function () { + return path.resolve(this.cwd, this._tempDirectory) +} + +NYC.prototype.processInfoDirectory = function () { + return path.resolve(this.tempDirectory(), 'processinfo') +} + +module.exports = NYC |