Repository: istanbuljs/nyc
Pull request #1071 (2019-04-17 14:15:34)

chore: Add test for `nyc --no-clean`.
Verify that running `nyc command1 && nyc --no-clean command2` causes
results results to be merged.

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

Coverage increased (+0.2%) to 96.833% when pulling **b174a38c70a92d72c710e3a2d7e747a8e3066110 on coreyfarrell:test-no-clean** into **ca37ffa74c146b77c5fa9dc3411901e96e8c3a17 on istanbuljs:master**.

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

Coverage increased (+0.2%) to 96.833% when pulling **b174a38c70a92d72c710e3a2d7e747a8e3066110 on coreyfarrell:test-no-clean** into **ca37ffa74c146b77c5fa9dc3411901e96e8c3a17 on istanbuljs:master**.


Patch:
diff --git a/bin/nyc.js b/bin/nyc.js
index cd22584..7c188e4 100755
--- a/bin/nyc.js
+++ b/bin/nyc.js
@@ -85,5 +85,6 @@ if ([
   })
 } else {
   // I don't have a clue what you're doing.
+  process.exitCode = 1
   yargs.showHelp()
 }
diff --git a/index.js b/index.js
index e0d67c3..8c9d8e9 100755
--- a/index.js
+++ b/index.js
@@ -32,564 +32,564 @@ if (/self-coverage/.test(__dirname)) {
   require('../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 = [].concat(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.extensions = [].concat(config.extension || [])
-    .concat('.js')
-    .map(ext => ext.toLowerCase())
-    .filter((item, pos, arr) => arr.indexOf(item) === pos)
-
-  this.exclude = testExclude({
-    cwd: this.cwd,
-    include: config.include,
-    exclude: config.exclude,
-    excludeNodeModules: config.excludeNodeModules !== false,
-    extension: this.extensions
-  })
-
-  this.sourceMaps = new SourceMaps({
-    cache: this.cache,
-    cacheDirectory: this.cacheDirectory
-  })
-
-  // require extensions can be provided as config in package.json.
-  this.require = [].concat(config.require || [])
-
-  this.transforms = this.extensions.reduce((transforms, ext) => {
-    transforms[ext] = this._createTransform(ext)
-    return transforms
-  }, {})
-
-  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 = {}
+function coverageFinder () {
+  var coverage = global.__coverage__
+  if (typeof __coverage__ === 'object') coverage = __coverage__
+  if (!coverage) coverage = global['__coverage__'] = {}
+  return coverage
 }
 
-NYC.prototype._createTransform = function (ext) {
-  var opts = {
-    salt: Hash.salt(this.config),
-    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)
-}
+class NYC {
+  constructor (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 = [].concat(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.extensions = [].concat(config.extension || [])
+      .concat('.js')
+      .map(ext => ext.toLowerCase())
+      .filter((item, pos, arr) => arr.indexOf(item) === pos)
+
+    this.exclude = testExclude({
+      cwd: this.cwd,
+      include: config.include,
+      exclude: config.exclude,
+      excludeNodeModules: config.excludeNodeModules !== false,
+      extension: this.extensions
+    })
 
-NYC.prototype._disableCachingTransform = function () {
-  return !(this.cache && this.config.isChildProcess)
-}
+    this.sourceMaps = new SourceMaps({
+      cache: this.cache,
+      cacheDirectory: this.cacheDirectory
+    })
 
-NYC.prototype._loadAdditionalModules = function () {
-  this.require.forEach(requireModule => {
-    // Attempt to require the module relative to the directory being instrumented.
-    // Then try other locations, e.g. the nyc node_modules folder.
-    require(resolveFrom.silent(this.cwd, requireModule) || requireModule)
-  })
-}
+    // require extensions can be provided as config in package.json.
+    this.require = [].concat(config.require || [])
 
-NYC.prototype.instrumenter = function () {
-  return this._instrumenter || (this._instrumenter = this._createInstrumenter())
-}
+    this.transforms = this.extensions.reduce((transforms, ext) => {
+      transforms[ext] = this._createTransform(ext)
+      return transforms
+    }, {})
 
-NYC.prototype._createInstrumenter = function () {
-  return this._instrumenterLib({
-    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.parserPlugins
-  })
-}
+    this.hookRequire = config.hookRequire
+    this.hookRunInContext = config.hookRunInContext
+    this.hookRunInThisContext = config.hookRunInThisContext
+    this.fakeRequire = null
 
-NYC.prototype.addFile = function (filename) {
-  const source = this._readTranspiledSource(filename)
-  this._maybeInstrumentSource(source, filename)
-}
+    this.processInfo = new ProcessInfo(config && config._processInfo)
+    this.rootId = this.processInfo.root || uuid()
 
-NYC.prototype._readTranspiledSource = function (filePath) {
-  var source = null
-  var ext = path.extname(filePath)
-  if (typeof Module._extensions[ext] === 'undefined') {
-    ext = '.js'
+    this.hashCache = {}
   }
-  Module._extensions[ext]({
-    _compile: function (content, filename) {
-      source = content
-    }
-  }, filePath)
-  return source
-}
 
-NYC.prototype.addAllFiles = function () {
-  this._loadAdditionalModules()
-
-  this.fakeRequire = true
-  this.exclude.globSync(this.cwd).forEach(relFile => {
-    const filename = path.resolve(this.cwd, relFile)
-    this.addFile(filename)
-    const coverage = coverageFinder()
-    const lastCoverage = this.instrumenter().lastFileCoverage()
-    if (lastCoverage) {
-      coverage[lastCoverage.path] = lastCoverage
+  _createTransform (ext) {
+    var opts = {
+      salt: Hash.salt(this.config),
+      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
     }
-  })
-  this.fakeRequire = false
-
-  this.writeCoverageFile()
-}
-
-NYC.prototype.instrumentAllFiles = function (input, output, cb) {
-  let inputDir = '.' + path.sep
-  const visitor = relFile => {
-    const inFile = path.resolve(inputDir, relFile)
-    const inCode = fs.readFileSync(inFile, 'utf-8')
-    const outCode = this._transform(inCode, inFile) || inCode
-
-    if (output) {
-      const mode = fs.statSync(inFile).mode
-      const outFile = path.resolve(output, relFile)
-      mkdirp.sync(path.dirname(outFile))
-      fs.writeFileSync(outFile, outCode)
-      fs.chmodSync(outFile, mode)
+    if (this._eagerInstantiation) {
+      opts.transform = this._transformFactory(this.cacheDirectory)
     } else {
-      console.log(outCode)
+      opts.factory = this._transformFactory.bind(this)
     }
+    return cachingTransform(opts)
   }
 
-  this._loadAdditionalModules()
-
-  try {
-    const stats = fs.lstatSync(input)
-    if (stats.isDirectory()) {
-      inputDir = input
+  _disableCachingTransform () {
+    return !(this.cache && this.config.isChildProcess)
+  }
 
-      const filesToInstrument = this.exclude.globSync(input)
+  _loadAdditionalModules () {
+    this.require.forEach(requireModule => {
+      // Attempt to require the module relative to the directory being instrumented.
+      // Then try other locations, e.g. the nyc node_modules folder.
+      require(resolveFrom.silent(this.cwd, requireModule) || requireModule)
+    })
+  }
 
-      if (this.config.completeCopy && output) {
-        const globOptions = { dot: true, nodir: true, ignore: ['**/.git', '**/.git/**', path.join(output, '**')] }
-        glob.sync(path.resolve(input, '**'), globOptions)
-          .forEach(src => cpFile.sync(src, path.join(output, path.relative(input, src))))
-      }
-      filesToInstrument.forEach(visitor)
-    } else {
-      visitor(input)
-    }
-  } catch (err) {
-    return cb(err)
+  instrumenter () {
+    return this._instrumenter || (this._instrumenter = this._createInstrumenter())
   }
-  cb()
-}
 
-NYC.prototype._transform = function (code, filename) {
-  const extname = path.extname(filename).toLowerCase()
-  const transform = this.transforms[extname] || (() => null)
+  _createInstrumenter () {
+    return this._instrumenterLib({
+      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.parserPlugins
+    })
+  }
 
-  return transform(code, { filename })
-}
+  addFile (filename) {
+    const source = this._readTranspiledSource(filename)
+    this._maybeInstrumentSource(source, filename)
+  }
 
-NYC.prototype._maybeInstrumentSource = function (code, filename) {
-  if (!this.exclude.shouldInstrument(filename)) {
-    return null
+  _readTranspiledSource (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
   }
 
-  return this._transform(code, filename)
-}
+  addAllFiles () {
+    this._loadAdditionalModules()
+
+    this.fakeRequire = true
+    this.exclude.globSync(this.cwd).forEach(relFile => {
+      const filename = path.resolve(this.cwd, relFile)
+      this.addFile(filename)
+      const coverage = coverageFinder()
+      const lastCoverage = this.instrumenter().lastFileCoverage()
+      if (lastCoverage) {
+        coverage[lastCoverage.path] = lastCoverage
+      }
+    })
+    this.fakeRequire = false
 
-NYC.prototype._transformFactory = function (cacheDir) {
-  const instrumenter = this.instrumenter()
-  let instrumented
+    this.writeCoverageFile()
+  }
 
-  return (code, metadata, hash) => {
-    const filename = metadata.filename
-    let sourceMap = null
+  instrumentAllFiles (input, output, cb) {
+    let inputDir = '.' + path.sep
+    const visitor = relFile => {
+      const inFile = path.resolve(inputDir, relFile)
+      const inCode = fs.readFileSync(inFile, 'utf-8')
+      const outCode = this._transform(inCode, inFile) || inCode
+
+      if (output) {
+        const mode = fs.statSync(inFile).mode
+        const outFile = path.resolve(output, relFile)
+        mkdirp.sync(path.dirname(outFile))
+        fs.writeFileSync(outFile, outCode)
+        fs.chmodSync(outFile, mode)
+      } else {
+        console.log(outCode)
+      }
+    }
 
-    if (this._sourceMap) sourceMap = this.sourceMaps.extractAndRegister(code, filename, hash)
+    this._loadAdditionalModules()
 
     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)
+      const stats = fs.lstatSync(input)
+      if (stats.isDirectory()) {
+        inputDir = input
+
+        const filesToInstrument = this.exclude.globSync(input)
+
+        if (this.config.completeCopy && output) {
+          const globOptions = { dot: true, nodir: true, ignore: ['**/.git', '**/.git/**', path.join(output, '**')] }
+          glob.sync(path.resolve(input, '**'), globOptions)
+            .forEach(src => cpFile.sync(src, path.join(output, path.relative(input, src))))
+        }
+        filesToInstrument.forEach(visitor)
       } else {
-        instrumented = code
+        visitor(input)
       }
+    } catch (err) {
+      return cb(err)
     }
-
-    if (this.fakeRequire) {
-      return 'function x () {}'
-    } else {
-      return instrumented
-    }
+    cb()
   }
-}
 
-NYC.prototype._handleJs = function (code, options) {
-  // ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624)
-  const filename = path.resolve(this.cwd, options.filename)
-  return this._maybeInstrumentSource(code, filename) || code
-}
+  _transform (code, filename) {
+    const extname = path.extname(filename).toLowerCase()
+    const transform = this.transforms[extname] || (() => null)
 
-NYC.prototype._addHook = function (type) {
-  const handleJs = this._handleJs.bind(this)
-  const dummyMatcher = () => true // we do all processing in transformer
-  libHook['hook' + type](dummyMatcher, handleJs, { extensions: this.extensions })
-}
+    return transform(code, { filename })
+  }
+
+  _maybeInstrumentSource (code, filename) {
+    if (!this.exclude.shouldInstrument(filename)) {
+      return null
+    }
 
-NYC.prototype._addRequireHooks = function () {
-  if (this.hookRequire) {
-    this._addHook('Require')
+    return this._transform(code, filename)
   }
-  if (this.hookRunInContext) {
-    this._addHook('RunInContext')
+
+  _transformFactory (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
+      }
+    }
   }
-  if (this.hookRunInThisContext) {
-    this._addHook('RunInThisContext')
+
+  _handleJs (code, options) {
+    // ensure the path has correct casing (see istanbuljs/nyc#269 and nodejs/node#6624)
+    const filename = path.resolve(this.cwd, options.filename)
+    return this._maybeInstrumentSource(code, filename) || code
   }
-}
 
-NYC.prototype.cleanup = function () {
-  if (!process.env.NYC_CWD) rimraf.sync(this.tempDirectory())
-}
+  _addHook (type) {
+    const handleJs = this._handleJs.bind(this)
+    const dummyMatcher = () => true // we do all processing in transformer
+    libHook['hook' + type](dummyMatcher, handleJs, { extensions: this.extensions })
+  }
 
-NYC.prototype.clearCache = function () {
-  if (this.cache) {
-    rimraf.sync(this.cacheDirectory)
+  _addRequireHooks () {
+    if (this.hookRequire) {
+      this._addHook('Require')
+    }
+    if (this.hookRunInContext) {
+      this._addHook('RunInContext')
+    }
+    if (this.hookRunInThisContext) {
+      this._addHook('RunInThisContext')
+    }
   }
-}
 
-NYC.prototype.createTempDirectory = function () {
-  mkdirp.sync(this.tempDirectory())
-  if (this.cache) mkdirp.sync(this.cacheDirectory)
+  cleanup () {
+    if (!process.env.NYC_CWD) rimraf.sync(this.tempDirectory())
+  }
 
-  mkdirp.sync(this.processInfoDirectory())
-}
+  clearCache () {
+    if (this.cache) {
+      rimraf.sync(this.cacheDirectory)
+    }
+  }
 
-NYC.prototype.reset = function () {
-  this.cleanup()
-  this.createTempDirectory()
-}
+  createTempDirectory () {
+    mkdirp.sync(this.tempDirectory())
+    if (this.cache) mkdirp.sync(this.cacheDirectory)
 
-NYC.prototype._wrapExit = function () {
-  // we always want to write coverage
-  // regardless of how the process exits.
-  onExit(() => {
-    this.writeCoverageFile()
-  }, { alwaysLast: true })
-}
+    mkdirp.sync(this.processInfoDirectory())
+  }
 
-NYC.prototype.wrap = function (bin) {
-  process.env.NYC_PROCESS_ID = this.processInfo.uuid
-  this._addRequireHooks()
-  this._wrapExit()
-  this._loadAdditionalModules()
-  return this
-}
+  reset () {
+    this.cleanup()
+    this.createTempDirectory()
+  }
 
-NYC.prototype.generateUniqueID = uuid
+  _wrapExit () {
+    // we always want to write coverage
+    // regardless of how the process exits.
+    onExit(() => {
+      this.writeCoverageFile()
+    }, { alwaysLast: true })
+  }
 
-NYC.prototype.writeCoverageFile = function () {
-  var coverage = coverageFinder()
-  if (!coverage) return
+  wrap (bin) {
+    process.env.NYC_PROCESS_ID = this.processInfo.uuid
+    this._addRequireHooks()
+    this._wrapExit()
+    this._loadAdditionalModules()
+    return this
+  }
 
-  // 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)
+  writeCoverageFile () {
+    var coverage = coverageFinder()
+    if (!coverage) return
 
-  if (this.cache) {
+    // Remove any files that should be excluded but snuck into the coverage
     Object.keys(coverage).forEach(function (absFile) {
-      if (this.hashCache[absFile] && coverage[absFile]) {
-        coverage[absFile].contentHash = this.hashCache[absFile]
+      if (!this.exclude.shouldInstrument(absFile)) {
+        delete coverage[absFile]
       }
     }, this)
-  } else {
-    coverage = this.sourceMaps.remapCoverage(coverage)
-  }
 
-  var id = this.processInfo.uuid
-  var coverageFilename = path.resolve(this.tempDirectory(), id + '.json')
+    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)
+    }
 
-  fs.writeFileSync(
-    coverageFilename,
-    JSON.stringify(coverage),
-    'utf-8'
-  )
+    var id = this.processInfo.uuid
+    var coverageFilename = path.resolve(this.tempDirectory(), id + '.json')
 
-  this.processInfo.coverageFilename = coverageFilename
-  this.processInfo.files = Object.keys(coverage)
+    fs.writeFileSync(
+      coverageFilename,
+      JSON.stringify(coverage),
+      'utf-8'
+    )
 
-  fs.writeFileSync(
-    path.resolve(this.processInfoDirectory(), id + '.json'),
-    JSON.stringify(this.processInfo),
-    'utf-8'
-  )
-}
+    this.processInfo.coverageFilename = coverageFilename
+    this.processInfo.files = Object.keys(coverage)
 
-function coverageFinder () {
-  var coverage = global.__coverage__
-  if (typeof __coverage__ === 'object') coverage = __coverage__
-  if (!coverage) coverage = global['__coverage__'] = {}
-  return coverage
-}
+    fs.writeFileSync(
+      path.resolve(this.processInfoDirectory(), id + '.json'),
+      JSON.stringify(this.processInfo),
+      'utf-8'
+    )
+  }
 
-NYC.prototype.getCoverageMapFromAllCoverageFiles = function (baseDirectory) {
-  const map = libCoverage.createCoverageMap({})
+  getCoverageMapFromAllCoverageFiles (baseDirectory) {
+    const map = libCoverage.createCoverageMap({})
 
-  this.eachReport(undefined, (report) => {
-    map.merge(report)
-  }, baseDirectory)
+    this.eachReport(undefined, (report) => {
+      map.merge(report)
+    }, baseDirectory)
 
-  map.data = this.sourceMaps.remapCoverage(map.data)
+    map.data = this.sourceMaps.remapCoverage(map.data)
 
-  // 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(filename => this.exclude.shouldInstrument(filename))
-  }
+    // 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(filename => this.exclude.shouldInstrument(filename))
+    }
 
-  return map
-}
+    return map
+  }
 
-NYC.prototype.report = function () {
-  var tree
-  var map = this.getCoverageMapFromAllCoverageFiles()
-  var context = libReport.createContext({
-    dir: this.reportDirectory(),
-    watermarks: this.config.watermarks
-  })
+  report () {
+    var tree
+    var map = this.getCoverageMapFromAllCoverageFiles()
+    var context = libReport.createContext({
+      dir: this.reportDirectory(),
+      watermarks: this.config.watermarks
+    })
 
-  tree = libReport.summarizers.pkg(map)
+    tree = libReport.summarizers.pkg(map)
 
-  this.reporter.forEach((_reporter) => {
-    tree.visit(reports.create(_reporter, {
-      skipEmpty: this.config.skipEmpty,
-      skipFull: this.config.skipFull
-    }), context)
-  })
+    this.reporter.forEach((_reporter) => {
+      tree.visit(reports.create(_reporter, {
+        skipEmpty: this.config.skipEmpty,
+        skipFull: this.config.skipFull
+      }), context)
+    })
 
-  if (this._showProcessTree) {
-    this.showProcessTree()
+    if (this._showProcessTree) {
+      this.showProcessTree()
+    }
   }
-}
 
-// XXX(@isaacs) Index generation should move to istanbul-lib-processinfo
-NYC.prototype.writeProcessIndex = function () {
-  const dir = this.processInfoDirectory()
-  const pidToUid = new Map()
-  const infoByUid = new Map()
-  const eidToUid = new Map()
-  const infos = fs.readdirSync(dir).filter(f => f !== 'index.json').map(f => {
-    try {
-      const info = JSON.parse(fs.readFileSync(path.resolve(dir, f), 'utf-8'))
-      info.children = []
-      pidToUid.set(info.uuid, info.pid)
-      pidToUid.set(info.pid, info.uuid)
-      infoByUid.set(info.uuid, info)
-      if (info.externalId) {
-        eidToUid.set(info.externalId, info.uuid)
+  // XXX(@isaacs) Index generation should move to istanbul-lib-processinfo
+  writeProcessIndex () {
+    const dir = this.processInfoDirectory()
+    const pidToUid = new Map()
+    const infoByUid = new Map()
+    const eidToUid = new Map()
+    const infos = fs.readdirSync(dir).filter(f => f !== 'index.json').map(f => {
+      try {
+        const info = JSON.parse(fs.readFileSync(path.resolve(dir, f), 'utf-8'))
+        info.children = []
+        pidToUid.set(info.uuid, info.pid)
+        pidToUid.set(info.pid, info.uuid)
+        infoByUid.set(info.uuid, info)
+        if (info.externalId) {
+          eidToUid.set(info.externalId, info.uuid)
+        }
+        return info
+      } catch (er) {
+        return null
       }
-      return info
-    } catch (er) {
-      return null
-    }
-  }).filter(Boolean)
-
-  // create all the parent-child links and write back the updated info
-  infos.forEach(info => {
-    if (info.parent) {
-      const parentInfo = infoByUid.get(info.parent)
-      if (parentInfo && !parentInfo.children.includes(info.uuid)) {
-        parentInfo.children.push(info.uuid)
+    }).filter(Boolean)
+
+    // create all the parent-child links and write back the updated info
+    infos.forEach(info => {
+      if (info.parent) {
+        const parentInfo = infoByUid.get(info.parent)
+        if (parentInfo && !parentInfo.children.includes(info.uuid)) {
+          parentInfo.children.push(info.uuid)
+        }
       }
-    }
-  })
-
-  // figure out which files were touched by each process.
-  const files = infos.reduce((files, info) => {
-    info.files.forEach(f => {
-      files[f] = files[f] || []
-      files[f].push(info.uuid)
     })
-    return files
-  }, {})
-
-  // build the actual index!
-  const index = infos.reduce((index, info) => {
-    index.processes[info.uuid] = {}
-    index.processes[info.uuid].parent = info.parent
-    if (info.externalId) {
-      if (index.externalIds[info.externalId]) {
-        throw new Error(`External ID ${info.externalId} used by multiple processes`)
-      }
-      index.processes[info.uuid].externalId = info.externalId
-      index.externalIds[info.externalId] = {
-        root: info.uuid,
-        children: info.children
+
+    // figure out which files were touched by each process.
+    const files = infos.reduce((files, info) => {
+      info.files.forEach(f => {
+        files[f] = files[f] || []
+        files[f].push(info.uuid)
+      })
+      return files
+    }, {})
+
+    // build the actual index!
+    const index = infos.reduce((index, info) => {
+      index.processes[info.uuid] = {}
+      index.processes[info.uuid].parent = info.parent
+      if (info.externalId) {
+        if (index.externalIds[info.externalId]) {
+          throw new Error(`External ID ${info.externalId} used by multiple processes`)
+        }
+        index.processes[info.uuid].externalId = info.externalId
+        index.externalIds[info.externalId] = {
+          root: info.uuid,
+          children: info.children
+        }
       }
-    }
-    index.processes[info.uuid].children = Array.from(info.children)
-    return index
-  }, { processes: {}, files: files, externalIds: {} })
-
-  // flatten the descendant sets of all the externalId procs
-  Object.keys(index.externalIds).forEach(eid => {
-    const { children } = index.externalIds[eid]
-    // push the next generation onto the list so we accumulate them all
-    for (let i = 0; i < children.length; i++) {
-      const nextGen = index.processes[children[i]].children
-      if (nextGen && nextGen.length) {
-        children.push(...nextGen.filter(uuid => children.indexOf(uuid) === -1))
+      index.processes[info.uuid].children = Array.from(info.children)
+      return index
+    }, { processes: {}, files: files, externalIds: {} })
+
+    // flatten the descendant sets of all the externalId procs
+    Object.keys(index.externalIds).forEach(eid => {
+      const { children } = index.externalIds[eid]
+      // push the next generation onto the list so we accumulate them all
+      for (let i = 0; i < children.length; i++) {
+        const nextGen = index.processes[children[i]].children
+        if (nextGen && nextGen.length) {
+          children.push(...nextGen.filter(uuid => children.indexOf(uuid) === -1))
+        }
       }
-    }
-  })
+    })
 
-  fs.writeFileSync(path.resolve(dir, 'index.json'), JSON.stringify(index))
-}
+    fs.writeFileSync(path.resolve(dir, 'index.json'), JSON.stringify(index))
+  }
 
-NYC.prototype.showProcessTree = function () {
-  var processTree = ProcessInfo.buildProcessTree(this._loadProcessInfos())
+  showProcessTree () {
+    var processTree = ProcessInfo.buildProcessTree(this._loadProcessInfos())
 
-  console.log(processTree.render(this))
-}
+    console.log(processTree.render(this))
+  }
 
-NYC.prototype.checkCoverage = function (thresholds, perFile) {
-  var map = this.getCoverageMapFromAllCoverageFiles()
-  var nyc = this
+  checkCoverage (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)
+    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)
+    }
+  }
+
+  _checkCoverage (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] + '%)')
+        }
+      }
     })
-  } else {
-    // ERROR: Coverage for lines (90.12%) does not meet global threshold (120%)
-    nyc._checkCoverage(map.getCoverageSummary(), thresholds)
   }
-}
 
-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] + '%)')
+  _loadProcessInfos () {
+    return fs.readdirSync(this.processInfoDirectory()).map(f => {
+      let data
+      try {
+        data = JSON.parse(fs.readFileSync(
+          path.resolve(this.processInfoDirectory(), f),
+          'utf-8'
+        ))
+      } catch (e) { // handle corrupt JSON output.
+        return null
       }
-    }
-  })
-}
+      if (f !== 'index.json') {
+        data.nodes = []
+        data = new ProcessInfo(data)
+      }
+      return { file: path.basename(f, '.json'), data: data }
+    }).filter(Boolean).reduce((infos, info) => {
+      infos[info.file] = info.data
+      return infos
+    }, {})
+  }
 
-NYC.prototype._loadProcessInfos = function () {
-  return fs.readdirSync(this.processInfoDirectory()).map(f => {
-    let data
-    try {
-      data = JSON.parse(fs.readFileSync(
-        path.resolve(this.processInfoDirectory(), f),
-        'utf-8'
-      ))
-    } catch (e) { // handle corrupt JSON output.
-      return null
-    }
-    if (f !== 'index.json') {
-      data.nodes = []
-      data = new ProcessInfo(data)
+  eachReport (filenames, iterator, baseDirectory) {
+    baseDirectory = baseDirectory || this.tempDirectory()
+
+    if (typeof filenames === 'function') {
+      iterator = filenames
+      filenames = undefined
     }
-    return { file: path.basename(f, '.json'), data: data }
-  }).filter(Boolean).reduce((infos, info) => {
-    infos[info.file] = info.data
-    return infos
-  }, {})
-}
 
-NYC.prototype.eachReport = function (filenames, iterator, baseDirectory) {
-  baseDirectory = baseDirectory || this.tempDirectory()
+    var _this = this
+    var files = filenames || fs.readdirSync(baseDirectory)
 
-  if (typeof filenames === 'function') {
-    iterator = filenames
-    filenames = undefined
-  }
+    files.forEach(function (f) {
+      var report
+      try {
+        report = JSON.parse(fs.readFileSync(
+          path.resolve(baseDirectory, f),
+          'utf-8'
+        ))
 
-  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 = {}
-    }
+        _this.sourceMaps.reloadCachedSourceMaps(report)
+      } catch (e) { // handle corrupt JSON output.
+        report = {}
+      }
 
-    iterator(report)
-  })
-}
+      iterator(report)
+    })
+  }
 
-NYC.prototype.loadReports = function (filenames) {
-  var reports = []
+  loadReports (filenames) {
+    var reports = []
 
-  this.eachReport(filenames, (report) => {
-    reports.push(report)
-  })
+    this.eachReport(filenames, (report) => {
+      reports.push(report)
+    })
 
-  return reports
-}
+    return reports
+  }
 
-NYC.prototype.tempDirectory = function () {
-  return path.resolve(this.cwd, this._tempDirectory)
-}
+  tempDirectory () {
+    return path.resolve(this.cwd, this._tempDirectory)
+  }
 
-NYC.prototype.reportDirectory = function () {
-  return path.resolve(this.cwd, this._reportDir)
-}
+  reportDirectory () {
+    return path.resolve(this.cwd, this._reportDir)
+  }
 
-NYC.prototype.processInfoDirectory = function () {
-  return path.resolve(this.tempDirectory(), 'processinfo')
+  processInfoDirectory () {
+    return path.resolve(this.tempDirectory(), 'processinfo')
+  }
 }
 
 module.exports = NYC


Test patch:
diff --git a/test/fixtures/cli/by-arg2.js b/test/fixtures/cli/by-arg2.js
new file mode 100644
index 0000000..d45cdef
--- /dev/null
+++ b/test/fixtures/cli/by-arg2.js
@@ -0,0 +1,8 @@
+const arg = process.argv[2];
+if (arg === '1') {
+  console.log('1')
+} else if (arg === '2') {
+  console.log('2')
+} else {
+  console.log('unexpected')
+}
diff --git a/test/nyc-integration.js b/test/nyc-integration.js
index 91bd358..58fa87d 100644
--- a/test/nyc-integration.js
+++ b/test/nyc-integration.js
@@ -12,7 +12,7 @@ const glob = require('glob')
 const isWindows = require('is-windows')()
 const rimraf = require('rimraf')
 const makeDir = require('make-dir')
-const spawn = require('child_process').spawn
+const { spawn, spawnSync } = require('child_process')
 const si = require('strip-indent')
 
 require('chai').should()
@@ -1079,6 +1079,23 @@ describe('the nyc cli', function () {
     })
   })
 
+  it('help shows to stderr when main command doesn\'t know what to do', () => {
+    const opts = {
+      cwd: fixturesCLI,
+      env,
+      encoding: 'utf8'
+    }
+
+    const help = spawnSync(process.execPath, [bin, '--help'], opts)
+    const unknown = spawnSync(process.execPath, [bin], opts)
+    help.status.should.equal(0)
+    unknown.status.should.equal(1)
+    help.stderr.should.equal('')
+    unknown.stdout.should.equal('')
+    help.stdout.should.not.equal('')
+    help.stdout.should.equal(unknown.stderr)
+  })
+
   describe('args', function () {
     it('does not interpret args intended for instrumented bin', function (done) {
       var args = [bin, '--silent', process.execPath, 'args.js', '--help', '--version']
@@ -1237,6 +1254,48 @@ describe('the nyc cli', function () {
     })
   })
 
+  it('handles --clean / --no-clean properly', () => {
+    rimraf.sync(path.resolve(fixturesCLI, '.nyc_output'))
+    const args = (doClean, arg) => [
+      bin,
+      doClean ? '--clean' : '--no-clean',
+      process.execPath,
+      './by-arg2.js',
+      arg
+    ]
+    const opts = {
+      cwd: fixturesCLI,
+      env: env,
+      encoding: 'utf8'
+    }
+
+    const proc1 = spawnSync(process.execPath, args(true, '1'), opts)
+    proc1.status.should.equal(0)
+    stdoutShouldEqual(proc1.stdout, `
+      1
+      ------------|----------|----------|----------|----------|-------------------|
+      File        |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
+      ------------|----------|----------|----------|----------|-------------------|
+      All files   |       50 |       25 |      100 |       50 |                   |
+       by-arg2.js |       50 |       25 |      100 |       50 |             4,5,7 |
+      ------------|----------|----------|----------|----------|-------------------|`
+    )
+    proc1.stderr.should.equal('')
+
+    const proc2 = spawnSync(process.execPath, args(false, '2'), opts)
+    proc2.status.should.equal(0)
+    stdoutShouldEqual(proc2.stdout, `
+      2
+      ------------|----------|----------|----------|----------|-------------------|
+      File        |  % Stmts | % Branch |  % Funcs |  % Lines | Uncovered Line #s |
+      ------------|----------|----------|----------|----------|-------------------|
+      All files   |    83.33 |       75 |      100 |    83.33 |                   |
+       by-arg2.js |    83.33 |       75 |      100 |    83.33 |                 7 |
+      ------------|----------|----------|----------|----------|-------------------|`
+    )
+    proc2.stderr.should.equal('')
+  })
+
   describe('noop instrumenter', function () {
     it('setting instrument to "false" configures noop instrumenter', function (done) {
       var args = [