Repository: istanbuljs/nyc
Pull request #1194 (2019-10-07 02:12:19)

feat: Use @istanbuljs/schema for yargs setup
BREAKING CHANGE: The `flow` and `jsx` parser plugins are no longer
enabled by default.

coveralls: 
[![Coverage Status](https://coveralls.io/builds/26144699/badge)](https://coveralls.io/builds/26144699)

Coverage increased (+0.009%) to 99.798% when pulling **b20fb398db76b3ad0e0428ed3486c400d722514f on coreyfarrell:yargs-schema** into **96b60b876b5e2f4ac13165f8672bc7da03f7b48a on istanbuljs:master**.


Patch:
diff --git a/lib/commands/check-coverage.js b/lib/commands/check-coverage.js
index 6d14c42..2816a58 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 5e59846..0000000
--- 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 0000000..8b967bd
--- /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 cdd89df..256e40d 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 <input> [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 b5b4ba6..0aefd11 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 <input-directory> [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 0c61e8a..4c2b297 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 28d61e0..883a12c 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 38b3f93..2280834 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 ff2f0a1..ad9ae4d 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",


Test patch:
diff --git a/test/config.js b/test/config.js
index 62b440c..881ac84 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 278d35a..dc630af 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 3f2ac11..3e4beee 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()