diff --git a/bin/nyc.js b/bin/nyc.js index d2d837d9c..8918a3503 100755 --- a/bin/nyc.js +++ b/bin/nyc.js @@ -17,42 +17,38 @@ var wrapper = require.resolve('./wrap.js') // we keep these values in a few different forms, // used in the various execution contexts of nyc: // reporting, instrumenting subprocesses, etc. -var yargs = configUtil.decorateYargs(configUtil.buildYargs()) +var yargs = configUtil.addCommandsAndHelp(configUtil.buildYargs()) var instrumenterArgs = processArgs.hideInstrumenteeArgs() var argv = yargs.parse(instrumenterArgs) -var config = configUtil.loadConfig(instrumenterArgs) if (argv._[0] === 'report') { - // run a report. - process.env.NYC_CWD = process.cwd() - - report(config) + // look in lib/commands/report.js for logic. } else if (argv._[0] === 'check-coverage') { - checkCoverage(config) + // look in lib/commands/check-coverage.js for logic. } else if (argv._[0] === 'instrument') { // look in lib/commands/instrument.js for logic. } else if (argv._.length) { // if instrument is set to false, // enable a noop instrumenter. - if (!config.instrument) config.instrumenter = './lib/instrumenters/noop' - else config.instrumenter = './lib/instrumenters/istanbul' + if (!argv.instrument) argv.instrumenter = './lib/instrumenters/noop' + else argv.instrumenter = './lib/instrumenters/istanbul' - var nyc = (new NYC(config)) - if (config.clean) { + var nyc = (new NYC(argv)) + if (argv.clean) { nyc.reset() } else { nyc.createTempDirectory() } - if (config.all) nyc.addAllFiles() + if (argv.all) nyc.addAllFiles() var env = { - NYC_CONFIG: JSON.stringify(config), + NYC_CONFIG: JSON.stringify(argv), NYC_CWD: process.cwd(), NYC_ROOT_ID: nyc.rootId, - NYC_INSTRUMENTER: config.instrumenter + NYC_INSTRUMENTER: argv.instrumenter } - if (config['babel-cache'] === false) { + if (argv['babel-cache'] === false) { // babel's cache interferes with some configurations, so is // disabled by default. opt in by setting babel-cache=true. env.BABEL_DISABLE_CACHE = process.env.BABEL_DISABLE_CACHE = '1' @@ -71,13 +67,13 @@ if (argv._[0] === 'report') { ), function (done) { var mainChildExitCode = process.exitCode - if (config.checkCoverage) { - checkCoverage(config) + if (argv.checkCoverage) { + checkCoverage(argv) process.exitCode = process.exitCode || mainChildExitCode - if (!config.silent) report(config) + if (!argv.silent) report(argv) return done() } else { - if (!config.silent) report(config) + if (!argv.silent) report(argv) return done() } }) diff --git a/lib/commands/check-coverage.js b/lib/commands/check-coverage.js new file mode 100644 index 000000000..c12667bbe --- /dev/null +++ b/lib/commands/check-coverage.js @@ -0,0 +1,46 @@ +var NYC +try { + NYC = require('../../index.covered.js') +} catch (e) { + NYC = require('../../index.js') +} + +exports.command = 'check-coverage' + +exports.describe = 'check whether coverage is within thresholds provided' + +exports.builder = function (yargs) { + yargs + .option('branches', { + default: 0, + description: 'what % of branches must be covered?' + }) + .option('functions', { + default: 0, + description: 'what % of functions must be covered?' + }) + .option('lines', { + default: 90, + description: 'what % of lines must be covered?' + }) + .option('statements', { + default: 0, + description: 'what % of statements must be covered?' + }) + .option('per-file', { + default: false, + description: 'check thresholds per file' + }) + .example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided") +} + +exports.handler = function (argv) { + process.env.NYC_CWD = process.cwd() + + ;(new NYC(argv)).checkCoverage({ + lines: argv.lines, + functions: argv.functions, + branches: argv.branches, + statements: argv.statements + }, argv['per-file']) +} diff --git a/lib/commands/instrument.js b/lib/commands/instrument.js index cbf70480e..cf1e97461 100644 --- a/lib/commands/instrument.js +++ b/lib/commands/instrument.js @@ -11,7 +11,6 @@ exports.describe = 'instruments a file or a directory tree and writes the instru exports.builder = function (yargs) { return yargs - .usage('$0 instrument [output]') .option('require', { alias: 'i', default: [], diff --git a/lib/commands/report.js b/lib/commands/report.js new file mode 100644 index 000000000..8289980b9 --- /dev/null +++ b/lib/commands/report.js @@ -0,0 +1,39 @@ +var NYC +try { + NYC = require('../../index.covered.js') +} catch (e) { + NYC = require('../../index.js') +} + +exports.command = 'report' + +exports.describe = 'run coverage report for .nyc_output' + +exports.builder = function (yargs) { + return yargs + .option('reporter', { + alias: 'r', + describe: 'coverage reporter(s) to use', + default: 'text' + }) + .option('report-dir', { + describe: 'directory to output coverage reports in', + default: 'coverage' + }) + .option('temp-directory', { + describe: 'directory to read raw coverage information from', + default: './.nyc_output' + }) + .option('show-process-tree', { + describe: 'display the tree of spawned processes', + default: false, + type: 'boolean' + }) + .example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage') +} + +exports.handler = function (argv) { + process.env.NYC_CWD = process.cwd() + var nyc = new NYC(argv) + nyc.report() +} diff --git a/lib/config-util.js b/lib/config-util.js index 3c733e083..54c8804e6 100644 --- a/lib/config-util.js +++ b/lib/config-util.js @@ -1,3 +1,5 @@ +'use strict' + const arrify = require('arrify') const fs = require('fs') const path = require('path') @@ -7,37 +9,29 @@ const Yargs = require('yargs/yargs') var Config = {} -// load config from a cascade of sources: -// * command line arguments -// * package.json -// * .nycrc -Config.loadConfig = function (argv, cwd) { +function guessCWD (cwd) { cwd = cwd || process.env.NYC_CWD || process.cwd() - var pkgPath = findUp.sync('package.json', {cwd: cwd}) - var rcPath = findUp.sync(['.nycrc', '.nycrc.json'], {cwd: cwd}) - var rcConfig = null + const pkgPath = findUp.sync('package.json', {cwd: cwd}) + if (pkgPath) { + cwd = path.dirname(pkgPath) + } + return cwd +} + +function loadConfig (argv, cwd) { + const rcPath = findUp.sync(['.nycrc', '.nycrc.json'], {cwd: cwd}) + let config = {} if (rcPath) { - rcConfig = JSON.parse( + config = JSON.parse( fs.readFileSync(rcPath, 'utf-8') ) } - if (pkgPath) { - cwd = path.dirname(pkgPath) - } - - var config = Config.buildYargs(cwd) - if (rcConfig) config.config(rcConfig) - config = config.parse(argv || []) - - // post-hoc, we convert several of the - // configuration settings to arrays, providing - // a consistent contract to index.js. - config.require = arrify(config.require) - config.extension = arrify(config.extension) - config.exclude = arrify(config.exclude) - config.include = arrify(config.include) + if (config.require) config.require = arrify(config.require) + if (config.extension) config.extension = arrify(config.extension) + if (config.exclude) config.exclude = arrify(config.exclude) + if (config.include) config.include = arrify(config.include) return config } @@ -45,56 +39,11 @@ Config.loadConfig = function (argv, cwd) { // build a yargs object, omitting any settings // that would cause the application to exit early. Config.buildYargs = function (cwd) { + cwd = guessCWD(cwd) + const config = loadConfig() return Yargs([]) - .usage('$0 [command] [options]\n\nrun your tests with the nyc bin to instrument them with coverage') - .command('report', 'run coverage report for .nyc_output', function (yargs) { - return yargs - .usage('$0 report [options]') - .option('reporter', { - alias: 'r', - describe: 'coverage reporter(s) to use', - default: 'text' - }) - .option('report-dir', { - describe: 'directory to output coverage reports in', - default: 'coverage' - }) - .option('temp-directory', { - describe: 'directory to read raw coverage information from', - default: './.nyc_output' - }) - .option('show-process-tree', { - describe: 'display the tree of spawned processes', - default: false, - type: 'boolean' - }) - .example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage') - }) - .command('check-coverage', 'check whether coverage is within thresholds provided', function (yargs) { - return yargs - .usage('$0 check-coverage [options]') - .option('branches', { - default: 0, - description: 'what % of branches must be covered?' - }) - .option('functions', { - default: 0, - description: 'what % of functions must be covered?' - }) - .option('lines', { - default: 90, - description: 'what % of lines must be covered?' - }) - .option('statements', { - default: 0, - description: 'what % of statements must be covered?' - }) - .option('per-file', { - default: false, - description: 'check thresholds per file' - }) - .example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided") - }) + .usage('$0 [command] [options]') + .usage('$0 [options] [bin-to-instrument]') .option('reporter', { alias: 'r', describe: 'coverage reporter(s) to use', @@ -139,7 +88,7 @@ Config.buildYargs = function (cwd) { .option('require', { alias: 'i', default: [], - describe: 'a list of additional modules that nyc should attempt to require in its subprocess, e.g., babel-register, babel-polyfill.', + describe: 'a list of additional modules that nyc should attempt to require in its subprocess, e.g., babel-register, babel-polyfill', global: false }) .option('eager', { @@ -240,24 +189,29 @@ Config.buildYargs = function (cwd) { default: './.nyc_output', global: false }) - .pkgConf('nyc', cwd || process.cwd()) + .pkgConf('nyc', cwd) .example('$0 npm test', 'instrument your tests with coverage') - .example('$0 --require babel-core/register npm test', 'instrument your tests with coverage and babel') + .example('$0 --require babel-core/register npm test', 'instrument your tests with coverage and transpile with Babel') .example('$0 report --reporter=text-lcov', 'output lcov report after running your tests') .epilog('visit https://git.io/vHysA for list of available reporters') - .boolean('help') .boolean('h') .boolean('version') + .config(config) + .help(false) + .version(false) } -// decorate yargs with all the actions -// that would make it exit: help, version, command. -Config.decorateYargs = function (yargs) { +// we add operations that would make yargs +// exit post-hoc, allowing for a multi-pass +// parsing step. +Config.addCommandsAndHelp = function (yargs) { return yargs .help('h') .alias('h', 'help') .version() + .command(require('../lib/commands/check-coverage')) .command(require('../lib/commands/instrument')) + .command(require('../lib/commands/report')) } module.exports = Config diff --git a/package.json b/package.json index 03f8c41d3..05d8620ba 100644 --- a/package.json +++ b/package.json @@ -5,7 +5,7 @@ "main": "index.js", "scripts": { "bundle": "bundle-dependencies update", - "pretest": "standard", + "posttest": "standard", "test": "npm run clean && npm run build && npm run instrument && npm run test-integration && npm run test-mocha && npm run report", "clean": "rimraf ./.nyc_output ./node_modules/.cache ./.self_coverage ./test/fixtures/.nyc_output ./test/fixtures/node_modules/.cache *covered.js ./lib/*covered.js", "build": "node ./build-tests", @@ -98,8 +98,8 @@ "signal-exit": "^3.0.1", "spawn-wrap": "^1.3.8", "test-exclude": "^4.1.1", - "yargs": "^8.0.1", - "yargs-parser": "^5.0.0" + "yargs": "^10.0.3", + "yargs-parser": "^8.0.0" }, "devDependencies": { "any-path": "^1.3.0", diff --git a/test/src/nyc-tap.js b/test/src/nyc-tap.js index bb57388d5..cf2ce38ae 100644 --- a/test/src/nyc-tap.js +++ b/test/src/nyc-tap.js @@ -45,45 +45,42 @@ require('tap').mochaGlobals() describe('nyc', function () { describe('cwd', function () { it('sets cwd to process.cwd() if no environment variable is set', function () { - var nyc = new NYC(configUtil.loadConfig()) + var nyc = new NYC(configUtil.buildYargs().parse()) nyc.cwd.should.eql(process.cwd()) }) it('uses NYC_CWD environment variable for cwd if it is set', function () { process.env.NYC_CWD = path.resolve(__dirname, '../fixtures') - var nyc = new NYC(configUtil.loadConfig()) + var nyc = new NYC(configUtil.buildYargs().parse()) nyc.cwd.should.equal(path.resolve(__dirname, '../fixtures')) }) it('will look upwards for package.json from cwd', function () { - var nyc = new NYC(configUtil.loadConfig([], __dirname)) + var nyc = new NYC(configUtil.buildYargs(__dirname).parse()) nyc.cwd.should.eql(path.join(__dirname, '../..')) }) - it('uses --cwd for cwd if it is set (highest priority and no look upwards for package.json) ', function () { - var nyc = new NYC(configUtil.loadConfig(['--cwd', __dirname], __dirname)) + it('uses --cwd for cwd if it is set (highest priority and does not look upwards for package.json) ', function () { + var nyc = new NYC(configUtil.buildYargs(__dirname).parse(['--cwd', __dirname])) nyc.cwd.should.eql(__dirname) }) }) describe('config', function () { it("loads 'exclude' patterns from package.json#nyc", function () { - var nyc = new NYC(configUtil.loadConfig([], path.resolve(__dirname, '../fixtures'))) + var nyc = new NYC(configUtil.buildYargs(path.resolve(__dirname, '../fixtures')).parse()) nyc.exclude.exclude.length.should.eql(8) }) it("loads 'extension' patterns from package.json#nyc", function () { - var nyc = new NYC(configUtil.loadConfig([], path.resolve(__dirname, '../fixtures/conf-multiple-extensions'))) + var nyc = new NYC(configUtil.buildYargs(path.resolve(__dirname, '../fixtures/conf-multiple-extensions')).parse()) nyc.extensions.length.should.eql(3) }) it("ignores 'include' option if it's falsy or []", function () { - var nyc1 = new NYC(configUtil.loadConfig( - [], - path.resolve(__dirname, '../fixtures/conf-empty') - )) + var nyc1 = new NYC(configUtil.buildYargs(path.resolve(__dirname, '../fixtures/conf-empty')).parse()) nyc1.exclude.include.should.equal(false) @@ -95,7 +92,7 @@ describe('nyc', function () { }) it("ignores 'exclude' option if it's falsy", function () { - var nyc1 = new NYC(configUtil.loadConfig([], path.resolve(__dirname, '../fixtures/conf-empty'))) + var nyc1 = new NYC(configUtil.buildYargs(path.resolve(__dirname, '../fixtures/conf-empty')).parse()) nyc1.exclude.exclude.length.should.eql(11) }) @@ -109,12 +106,12 @@ describe('nyc', function () { describe('shouldInstrumentFile', function () { it('should exclude appropriately with defaults', function () { - var nyc = new NYC(configUtil.loadConfig([ + var nyc = new NYC(configUtil.buildYargs('/cwd').parse([ '--exclude=test/**', '--exclude=test{,-*}.js', '--exclude=**/*.test.js', '--exclude=**/__tests__/**' - ], '/cwd')) + ])) // nyc always excludes "node_modules/**" nyc.exclude.shouldInstrument('/cwd/foo', 'foo').should.equal(true) @@ -131,7 +128,7 @@ describe('nyc', function () { }) it('should exclude appropriately with config.exclude', function () { - var nyc = new NYC(configUtil.loadConfig([], fixtures)) + var nyc = new NYC(configUtil.buildYargs(fixtures).parse()) // fixtures/package.json configures excludes: "blarg", "blerg" nyc.exclude.shouldInstrument('blarg', 'blarg').should.equal(false) @@ -142,31 +139,31 @@ describe('nyc', function () { }) it('should exclude outside of the current working directory', function () { - var nyc = new NYC(configUtil.loadConfig([], '/cwd/foo/')) + var nyc = new NYC(configUtil.buildYargs('/cwd/foo/').parse()) nyc.exclude.shouldInstrument('/cwd/bar', '../bar').should.equal(false) }) it('should not exclude if the current working directory is inside node_modules', function () { - var nyc = new NYC(configUtil.loadConfig([], '/cwd/node_modules/foo/')) + var nyc = new NYC(configUtil.buildYargs('/cwd/node_modules/foo/').parse()) nyc.exclude.shouldInstrument('/cwd/node_modules/foo/bar', './bar').should.equal(true) nyc.exclude.shouldInstrument('/cwd/node_modules/foo/bar', '.\\bar').should.equal(true) }) it('allows files to be explicitly included, rather than excluded', function () { - var nyc = new NYC(configUtil.loadConfig(['--include=foo.js'], '/cwd/')) + var nyc = new NYC(configUtil.buildYargs('/cwd/').parse(['--include=foo.js'])) nyc.exclude.shouldInstrument('/cwd/foo.js', 'foo.js').should.equal(true) nyc.exclude.shouldInstrument('/cwd/index.js', 'index.js').should.equal(false) }) it('exclude overrides include', function () { - var nyc = new NYC(configUtil.loadConfig([ + var nyc = new NYC(configUtil.buildYargs('/cwd/').parse([ '--include=foo.js', '--include=test.js', '--exclude=**/node_modules/**', '--exclude=test/**', '--exclude=test{,-*}.js' - ], '/cwd/')) + ])) nyc.exclude.shouldInstrument('/cwd/foo.js', 'foo.js').should.equal(true) nyc.exclude.shouldInstrument('/cwd/test.js', 'test.js').should.equal(false) @@ -175,7 +172,7 @@ describe('nyc', function () { describe('wrap', function () { it('wraps modules with coverage counters when they are required', function () { - var nyc = new NYC(configUtil.loadConfig()) + var nyc = new NYC(configUtil.buildYargs().parse()) nyc.reset() nyc.wrap() @@ -192,7 +189,7 @@ describe('nyc', function () { // the `require` call to istanbul is deferred, loaded here so it doesn't mess with the hooks callCount require('istanbul-lib-instrument') - var nyc = new NYC(configUtil.loadConfig()) + var nyc = new NYC(configUtil.buildYargs().parse()) nyc.reset() nyc.wrap() @@ -209,7 +206,7 @@ describe('nyc', function () { describe('produce source map', function () { it('handles stack traces', function () { - var nyc = new NYC(configUtil.loadConfig('--produce-source-map')) + var nyc = new NYC(configUtil.buildYargs().parse('--produce-source-map')) nyc.reset() nyc.wrap() @@ -218,22 +215,20 @@ describe('nyc', function () { }) it('does not handle stack traces when disabled', function () { - var nyc = new NYC(configUtil.loadConfig()) + var nyc = new NYC(configUtil.buildYargs().parse()) nyc.reset() nyc.wrap() var check = require('../fixtures/stack-trace') check().should.match(/stack-trace.js:1:/) }) - - // TODO: add test for merge source-map logic. }) describe('compile handlers for custom extensions are assigned', function () { it('assigns a function to custom extensions', function () { - var nyc = new NYC(configUtil.loadConfig([], + var nyc = new NYC(configUtil.buildYargs( path.resolve(__dirname, '../fixtures/conf-multiple-extensions') - )) + ).parse()) nyc.reset() nyc.wrap() @@ -248,9 +243,9 @@ describe('nyc', function () { // the `require` call to istanbul is deferred, loaded here so it doesn't mess with the hooks callCount require('istanbul-lib-instrument') - var nyc = new NYC(configUtil.loadConfig([], + var nyc = new NYC(configUtil.buildYargs( path.resolve(__dirname, '../fixtures/conf-multiple-extensions') - )) + ).parse()) sinon.spy(nyc, '_handleJs') @@ -266,7 +261,7 @@ describe('nyc', function () { }) function testSignal (signal, done) { - var nyc = (new NYC(configUtil.loadConfig([], fixtures))) + var nyc = (new NYC(configUtil.buildYargs(fixtures).parse())) var proc = spawn(process.execPath, [bin, './' + signal + '.js'], { cwd: fixtures, @@ -294,7 +289,7 @@ describe('nyc', function () { }) it('does not output coverage for files that have not been included, by default', function (done) { - var nyc = (new NYC(configUtil.loadConfig([], process.cwd()))) + var nyc = (new NYC(configUtil.buildYargs(process.cwd()).parse())) nyc.wrap() nyc.reset() @@ -308,7 +303,7 @@ describe('nyc', function () { describe('report', function () { it('allows coverage report to be output in an alternative directory', function (done) { - var nyc = new NYC(configUtil.loadConfig( + var nyc = new NYC(configUtil.buildYargs().parse( ['--report-dir=./alternative-report', '--reporter=lcov'] )) nyc.reset() @@ -330,7 +325,7 @@ describe('nyc', function () { describe('addAllFiles', function () { it('outputs an empty coverage report for all files that are not excluded', function (done) { - var nyc = new NYC(configUtil.loadConfig([], fixtures)) + var nyc = new NYC(configUtil.buildYargs(fixtures).parse()) nyc.reset() nyc.addAllFiles() @@ -348,7 +343,7 @@ describe('nyc', function () { it('outputs an empty coverage report for multiple configured extensions', function (done) { var cwd = path.resolve(fixtures, './conf-multiple-extensions') - var nyc = new NYC(configUtil.loadConfig([], cwd)) + var nyc = new NYC(configUtil.buildYargs(cwd).parse()) nyc.reset() nyc.addAllFiles() @@ -373,7 +368,7 @@ describe('nyc', function () { }) it('tracks coverage appropriately once the file is required', function (done) { - var nyc = (new NYC(configUtil.loadConfig([], fixtures))) + var nyc = (new NYC(configUtil.buildYargs(fixtures).parse())) nyc.reset() nyc.wrap() @@ -401,7 +396,7 @@ describe('nyc', function () { 'utf-8' ) - var nyc = (new NYC(configUtil.loadConfig(['--require', './test/fixtures/transpile-hook'], fixtures))) + var nyc = (new NYC(configUtil.buildYargs(fixtures).parse(['--require', './test/fixtures/transpile-hook']))) nyc.reset() nyc.addAllFiles() @@ -426,10 +421,10 @@ describe('nyc', function () { 'utf-8' ) - var nyc = (new NYC(configUtil.loadConfig([ + var nyc = (new NYC(configUtil.buildYargs(fixtures).parse([ '--require=./test/fixtures/transpile-hook', '--extension=.whatever' - ], fixtures))) + ]))) nyc.reset() nyc.addAllFiles() @@ -449,7 +444,7 @@ describe('nyc', function () { describe('cache', function () { it('handles collisions', function (done) { - var nyc = new NYC(configUtil.loadConfig([], fixtures)) + var nyc = new NYC(configUtil.buildYargs(fixtures).parse()) nyc.clearCache() var args = [bin, process.execPath, './cache-collision-runner.js']