From fd40d49331665d936b86f30e9a873ba80071b770 Mon Sep 17 00:00:00 2001 From: Corey Farrell Date: Sun, 6 Oct 2019 23:07:23 -0400 Subject: [PATCH] feat: Use @istanbuljs/schema for yargs setup (#1194) BREAKING CHANGE: The `flow` and `jsx` parser plugins are no longer enabled by default. --- lib/commands/check-coverage.js | 59 +-------- lib/commands/cli-wrapper.js | 10 -- lib/commands/helpers.js | 61 +++++++++ lib/commands/instrument.js | 91 +------------- lib/commands/merge.js | 17 +-- lib/commands/report.js | 97 +------------- lib/config-util.js | 223 +-------------------------------- package-lock.json | 6 +- package.json | 2 + test/config.js | 2 +- test/nyc-integration.js | 1 + test/source-map-support.js | 8 +- 12 files changed, 97 insertions(+), 480 deletions(-) delete mode 100644 lib/commands/cli-wrapper.js create mode 100644 lib/commands/helpers.js diff --git a/lib/commands/check-coverage.js b/lib/commands/check-coverage.js index 6d14c428b..2816a5876 100644 --- a/lib/commands/check-coverage.js +++ b/lib/commands/check-coverage.js @@ -1,6 +1,5 @@ -const testExclude = require('test-exclude') const NYC = require('../../index.js') -const cliWrapper = require('./cli-wrapper.js') +const { cliWrapper, setupOptions } = require('./helpers.js') exports.command = 'check-coverage' @@ -9,61 +8,9 @@ exports.describe = 'check whether coverage is within thresholds provided' exports.builder = function (yargs) { yargs .demandCommand(0, 0) - .option('exclude', { - alias: 'x', - default: testExclude.defaultExclude, - describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported, node_modules is always excluded', - global: false - }) - .option('exclude-node-modules', { - default: true, - type: 'boolean', - describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default', - global: false - }) - .option('exclude-after-remap', { - default: true, - type: 'boolean', - description: 'should exclude logic be performed after the source-map remaps filenames?', - global: false - }) - .option('include', { - alias: 'n', - default: [], - describe: 'a list of specific files that should be covered, glob patterns are supported', - global: false - }) - .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' - }) - .option('temp-dir', { - alias: 't', - describe: 'directory to read raw coverage information from', - default: './.nyc_output', - global: false - }) - .option('temp-directory', { - hidden: true, - global: false - }) .example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided") + + setupOptions(yargs, 'check-coverage') } exports.handler = cliWrapper(async argv => { diff --git a/lib/commands/cli-wrapper.js b/lib/commands/cli-wrapper.js deleted file mode 100644 index 5e5984653..000000000 --- a/lib/commands/cli-wrapper.js +++ /dev/null @@ -1,10 +0,0 @@ -'use strict' - -module.exports = execute => { - return argv => { - execute(argv).catch(error => { - console.error(error.message) - process.exit(1) - }) - } -} diff --git a/lib/commands/helpers.js b/lib/commands/helpers.js new file mode 100644 index 000000000..8b967bd81 --- /dev/null +++ b/lib/commands/helpers.js @@ -0,0 +1,61 @@ +'use strict' + +const decamelize = require('decamelize') +const schema = require('@istanbuljs/schema') + +/* These options still need to be connected to the instrumenter + * Disabling them for now also avoids the issue with OSX cutting + * off the error help screen at 8192 characters. + */ +const blockOptions = [ + 'coverageVariable', + 'coverageGlobalScope', + 'coverageGlobalScopeFunc' +] + +module.exports = { + setupOptions (yargs, command, cwd) { + Object.entries(schema.nyc.properties).forEach(([name, setup]) => { + if (blockOptions.includes(name)) { + return + } + + const option = { + description: setup.description, + default: setup.default, + type: setup.type + } + + if (name === 'cwd') { + if (command !== null) { + return + } + + option.default = cwd + option.global = true + } + + if (option.type === 'array') { + option.type = 'string' + } + + if ('nycAlias' in setup) { + option.alias = setup.nycAlias + } + + const optionName = decamelize(name, '-') + yargs.option(optionName, option) + if (!setup.nycCommands.includes(command)) { + yargs.hide(optionName) + } + }) + }, + cliWrapper (execute) { + return argv => { + execute(argv).catch(error => { + console.error(error.message) + process.exit(1) + }) + } + } +} diff --git a/lib/commands/instrument.js b/lib/commands/instrument.js index cdd89df12..256e40db4 100644 --- a/lib/commands/instrument.js +++ b/lib/commands/instrument.js @@ -2,101 +2,18 @@ const NYC = require('../../index.js') const path = require('path') const { promisify } = require('util') const rimraf = promisify(require('rimraf')) -const testExclude = require('test-exclude') -const cliWrapper = require('./cli-wrapper.js') +const { cliWrapper, setupOptions } = require('./helpers.js') exports.command = 'instrument [output]' exports.describe = 'instruments a file or a directory tree and writes the instrumented code to the desired output location' exports.builder = function (yargs) { - return yargs + yargs .demandCommand(0, 0) - .positional('input', { - describe: 'file or directory to instrument', - type: 'text' - }) - .positional('output', { - describe: 'directory to output instrumented files', - type: 'text' - }) - .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.' - }) - .option('extension', { - alias: 'e', - default: ['.cjs', '.mjs', '.ts', '.tsx', '.jsx'], - describe: 'a list of extensions that nyc should handle in addition to .js' - }) - .option('source-map', { - default: true, - type: 'boolean', - describe: 'should nyc detect and handle source maps?' - }) - .option('produce-source-map', { - default: false, - type: 'boolean', - describe: "should nyc's instrumenter produce source maps?" - }) - .option('compact', { - default: true, - type: 'boolean', - describe: 'should the output be compacted?' - }) - .option('preserve-comments', { - default: true, - type: 'boolean', - describe: 'should comments be preserved in the output?' - }) - .option('instrument', { - default: true, - type: 'boolean', - describe: 'should nyc handle instrumentation?' - }) - .option('in-place', { - default: false, - type: 'boolean', - describe: 'should nyc run the instrumentation in place?' - }) - .option('exit-on-error', { - default: false, - type: 'boolean', - describe: 'should nyc exit when an instrumentation failure occurs?' - }) - .option('include', { - alias: 'n', - default: [], - describe: 'a list of specific files and directories that should be instrumented, glob patterns are supported' - }) - .option('exclude', { - alias: 'x', - default: testExclude.defaultExclude, - describe: 'a list of specific files and directories that should not be instrumented, glob patterns are supported' - }) - .option('exclude-node-modules', { - default: true, - type: 'boolean', - describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default', - global: false - }) - .option('es-modules', { - default: true, - type: 'boolean', - description: 'tell the instrumenter to treat files as ES Modules' - }) - .option('delete', { - describe: 'should the output folder be deleted before instrumenting files?', - default: false, - type: 'boolean' - }) - .option('complete-copy', { - describe: 'should nyc copy all files from input to output as well as instrumented files?', - default: false, - type: 'boolean' - }) .example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output') + + setupOptions(yargs, 'instrument') } exports.handler = cliWrapper(async argv => { diff --git a/lib/commands/merge.js b/lib/commands/merge.js index b5b4ba654..0aefd11ec 100644 --- a/lib/commands/merge.js +++ b/lib/commands/merge.js @@ -2,7 +2,7 @@ const path = require('path') const makeDir = require('make-dir') const fs = require('../fs-promises') -const cliWrapper = require('./cli-wrapper.js') +const { cliWrapper, setupOptions } = require('./helpers.js') const NYC = require('../../index.js') @@ -11,8 +11,9 @@ exports.command = 'merge [output-file]' exports.describe = 'merge istanbul format coverage output in a given folder' exports.builder = function (yargs) { - return yargs + yargs .demandCommand(0, 0) + .example('$0 merge ./out coverage.json', 'merge together reports in ./out and output as coverage.json') .positional('input-directory', { describe: 'directory containing multiple istanbul coverage files', type: 'text', @@ -23,15 +24,9 @@ exports.builder = function (yargs) { type: 'text', default: 'coverage.json' }) - .option('temp-dir', { - alias: 't', - describe: 'directory to read raw coverage information from', - default: './.nyc_output' - }) - .option('temp-directory', { - hidden: true - }) - .example('$0 merge ./out coverage.json', 'merge together reports in ./out and output as coverage.json') + + setupOptions(yargs, 'merge') + yargs.default('exclude-after-remap', false) } exports.handler = cliWrapper(async argv => { diff --git a/lib/commands/report.js b/lib/commands/report.js index 0c61e8aee..4c2b29752 100644 --- a/lib/commands/report.js +++ b/lib/commands/report.js @@ -1,105 +1,16 @@ -const testExclude = require('test-exclude') const NYC = require('../../index.js') -const cliWrapper = require('./cli-wrapper.js') +const { cliWrapper, setupOptions } = require('./helpers.js') exports.command = 'report' exports.describe = 'run coverage report for .nyc_output' exports.builder = function (yargs) { - return yargs + yargs .demandCommand(0, 0) - .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-dir', { - alias: 't', - describe: 'directory to read raw coverage information from', - default: './.nyc_output' - }) - .option('temp-directory', { - hidden: true - }) - .option('exclude', { - alias: 'x', - default: testExclude.defaultExclude, - describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported, node_modules is always excluded', - global: false - }) - .option('exclude-node-modules', { - default: true, - type: 'boolean', - describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default', - global: false - }) - .option('exclude-after-remap', { - default: true, - type: 'boolean', - description: 'should exclude logic be performed after the source-map remaps filenames?', - global: false - }) - .option('include', { - alias: 'n', - default: [], - describe: 'a list of specific files that should be covered, glob patterns are supported', - global: false - }) - .option('extension', { - alias: 'e', - default: ['.cjs', '.mjs', '.ts', '.tsx', '.jsx'], - describe: 'a list of extensions that nyc should handle in addition to .js', - global: false - }) - .option('show-process-tree', { - describe: 'display the tree of spawned processes', - default: false, - type: 'boolean' - }) - .option('skip-empty', { - describe: 'don\'t show empty files (no lines of code) in report', - default: false, - type: 'boolean', - global: false - }) - .option('check-coverage', { - type: 'boolean', - default: false, - describe: 'check whether coverage is within thresholds provided', - global: false - }) - .option('branches', { - default: 0, - description: 'what % of branches must be covered?', - global: false - }) - .option('functions', { - default: 0, - description: 'what % of functions must be covered?', - global: false - }) - .option('lines', { - default: 90, - description: 'what % of lines must be covered?', - global: false - }) - .option('statements', { - default: 0, - description: 'what % of statements must be covered?', - global: false - }) - .option('per-file', { - default: false, - type: 'boolean', - description: 'check thresholds per file', - global: false - }) .example('$0 report --reporter=lcov', 'output an HTML lcov report to ./coverage') + + setupOptions(yargs, 'report') } exports.handler = cliWrapper(async argv => { diff --git a/lib/config-util.js b/lib/config-util.js index 28d61e048..883a12caa 100644 --- a/lib/config-util.js +++ b/lib/config-util.js @@ -2,9 +2,9 @@ const path = require('path') const findUp = require('find-up') -const testExclude = require('test-exclude') const Yargs = require('yargs/yargs') +const { setupOptions } = require('./commands/helpers') const processArgs = require('./process-args') const { loadNycConfig } = require('@istanbuljs/load-nyc-config') @@ -23,222 +23,11 @@ async function processConfig (cwd) { const yargs = Yargs([]) .usage('$0 [command] [options]') .usage('$0 [options] [bin-to-instrument]') - .option('reporter', { - alias: 'r', - describe: 'coverage reporter(s) to use', - default: 'text', - global: false - }) - .option('report-dir', { - describe: 'directory to output coverage reports in', - default: 'coverage', - global: false - }) - .option('silent', { - alias: 's', - default: false, - type: 'boolean', - describe: "don't output a report after tests finish running", - global: false - }) - .option('all', { - alias: 'a', - default: false, - type: 'boolean', - describe: 'whether or not to instrument all files of the project (not just the ones touched by your test suite)', - global: false - }) - .option('exclude', { - alias: 'x', - default: testExclude.defaultExclude, - describe: 'a list of specific files and directories that should be excluded from coverage, glob patterns are supported, node_modules is always excluded', - global: false - }) - .option('exclude-after-remap', { - default: true, - type: 'boolean', - description: 'should exclude logic be performed after the source-map remaps filenames?', - global: false - }) - .option('exclude-node-modules', { - default: true, - type: 'boolean', - describe: 'whether or not to exclude all node_module folders (i.e. **/node_modules/**) by default', - global: false - }) - .option('include', { - alias: 'n', - default: [], - describe: 'a list of specific files that should be covered, glob patterns are supported', - global: false - }) - .option('cwd', { - describe: 'working directory used when resolving paths', - default: 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', - global: false - }) - .option('eager', { - default: false, - type: 'boolean', - describe: 'instantiate the instrumenter at startup (see https://git.io/vMKZ9)', - global: false - }) - .option('cache', { - alias: 'c', - default: true, - type: 'boolean', - describe: 'cache instrumentation results for improved performance', - global: false - }) - .option('cache-dir', { - describe: 'explicitly set location for instrumentation cache', - global: false - }) - .option('babel-cache', { - default: false, - type: 'boolean', - describe: 'cache babel transpilation results for improved performance', - global: false - }) - .option('es-modules', { - default: true, - type: 'boolean', - describe: 'tell the instrumenter to treat files as ES Modules', - global: false - }) - .option('extension', { - alias: 'e', - default: ['.cjs', '.mjs', '.ts', '.tsx', '.jsx'], - describe: 'a list of extensions that nyc should handle in addition to .js', - global: false - }) - .option('check-coverage', { - type: 'boolean', - default: false, - describe: 'check whether coverage is within thresholds provided', - global: false - }) - .option('branches', { - default: 0, - description: 'what % of branches must be covered?', - global: false - }) - .option('functions', { - default: 0, - description: 'what % of functions must be covered?', - global: false - }) - .option('lines', { - default: 90, - description: 'what % of lines must be covered?', - global: false - }) - .option('statements', { - default: 0, - description: 'what % of statements must be covered?', - global: false - }) - .option('source-map', { - default: true, - type: 'boolean', - description: 'should nyc detect and handle source maps?', - global: false - }) - .option('per-file', { - default: false, - type: 'boolean', - description: 'check thresholds per file', - global: false - }) - .option('produce-source-map', { - default: false, - type: 'boolean', - description: "should nyc's instrumenter produce source maps?", - global: false - }) - .option('compact', { - default: true, - type: 'boolean', - description: 'should the output be compacted?' - }) - .option('preserve-comments', { - default: true, - type: 'boolean', - description: 'should comments be preserved in the output?' - }) - .option('instrument', { - default: true, - type: 'boolean', - description: 'should nyc handle instrumentation?', - global: false - }) - .option('hook-require', { - default: true, - type: 'boolean', - description: 'should nyc wrap require?', - global: false - }) - .option('hook-run-in-context', { - default: false, - type: 'boolean', - description: 'should nyc wrap vm.runInContext?', - global: false - }) - .option('hook-run-in-this-context', { - default: false, - type: 'boolean', - description: 'should nyc wrap vm.runInThisContext?', - global: false - }) - .option('show-process-tree', { - describe: 'display the tree of spawned processes', - default: false, - type: 'boolean', - global: false - }) - .option('use-spawn-wrap', { - describe: 'use spawn-wrap instead of setting process.env.NODE_OPTIONS', - default: false, - type: 'boolean', - global: false - }) - .option('clean', { - describe: 'should the .nyc_output folder be cleaned before executing tests', - default: true, - type: 'boolean', - global: false - }) - .option('nycrc-path', { - description: 'specify a different .nycrc path', - global: false - }) - .option('temp-dir', { - alias: 't', - describe: 'directory to output raw coverage information to', - default: './.nyc_output', - global: false - }) - .option('temp-directory', { - hidden: true, - global: false - }) - .option('skip-empty', { - describe: 'don\'t show empty files (no lines of code) in report', - default: false, - type: 'boolean', - global: false - }) - .option('skip-full', { - describe: 'don\'t show files with 100% statement, branch, and function coverage', - default: false, - type: 'boolean', - global: false - }) + .showHidden(false) + + setupOptions(yargs, null, cwd) + + yargs .example('$0 npm test', 'instrument your tests with coverage') .example('$0 --require @babel/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') diff --git a/package-lock.json b/package-lock.json index 38b3f93de..228083443 100644 --- a/package-lock.json +++ b/package-lock.json @@ -121,9 +121,9 @@ } }, "@istanbuljs/schema": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.0.tgz", - "integrity": "sha512-juX6y33AgnEkto3n3AhViX70IP19N3x6Np1eIikPcH5ATqj8ntLWD3MsOF5ohg0h+mAv6IFUfABBHjmve9dncg==" + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/@istanbuljs/schema/-/schema-0.1.1.tgz", + "integrity": "sha512-Dt63B/JcXUxwnngdR8BYqSuQBX0HVXgLZtGMsKAAIQLe0whQdypOjsuqcSf8HR5M00/L8NuRC/+dZb6rEc+b5g==" }, "JSONStream": { "version": "1.3.5", diff --git a/package.json b/package.json index ff2f0a10f..ad9ae4d09 100644 --- a/package.json +++ b/package.json @@ -69,9 +69,11 @@ "license": "ISC", "dependencies": { "@istanbuljs/load-nyc-config": "^1.0.0-alpha.0", + "@istanbuljs/schema": "^0.1.1", "caching-transform": "^4.0.0", "convert-source-map": "^1.6.0", "cp-file": "^7.0.0", + "decamelize": "^1.2.0", "find-cache-dir": "^3.0.0", "find-up": "^4.1.0", "foreground-child": "^2.0.0", diff --git a/test/config.js b/test/config.js index 62b440c87..881ac8459 100644 --- a/test/config.js +++ b/test/config.js @@ -35,7 +35,7 @@ test("ignores 'include' option if it's falsy or []", async t => { test("ignores 'exclude' option if it's falsy", async t => { const nyc = new NYC(await parseArgv(path.resolve(__dirname, './fixtures/conf-empty'))) - t.strictEqual(nyc.exclude.exclude.length, 15) + t.strictEqual(nyc.exclude.exclude.length, 18) }) test("allows for empty 'exclude'", async t => { diff --git a/test/nyc-integration.js b/test/nyc-integration.js index 278d35afa..dc630afc5 100644 --- a/test/nyc-integration.js +++ b/test/nyc-integration.js @@ -570,6 +570,7 @@ t.test('combines multiple coverage reports', async t => { }) const mergedCoverage = require('./fixtures/cli/coverage') + console.log(Object.keys(mergedCoverage)) // the combined reports should have 100% function // branch and statement coverage. t.strictDeepEqual( diff --git a/test/source-map-support.js b/test/source-map-support.js index 3f2ac11b5..3e4beee18 100644 --- a/test/source-map-support.js +++ b/test/source-map-support.js @@ -14,7 +14,9 @@ require('source-map-support').install({ hookRequire: true }) t.beforeEach(resetState) t.test('handles stack traces', async t => { - const nyc = new NYC(await parseArgv(undefined, ['--produce-source-map'])) + const nyc = new NYC(await parseArgv(undefined, [ + '--produce-source-map=true' + ])) await nyc.reset() nyc.wrap() @@ -24,7 +26,9 @@ t.test('handles stack traces', async t => { }) t.test('does not handle stack traces when disabled', async t => { - const nyc = new NYC(await parseArgv()) + const nyc = new NYC(await parseArgv(undefined, [ + '--produce-source-map=false' + ])) await nyc.reset() nyc.wrap()