Skip to content

Commit

Permalink
feat: adds instrument command line option (#298)
Browse files Browse the repository at this point in the history
* feat: adds instrument command line option

* fix: load instrumented version of NYC; fix option description based on @kentcdodds' code review

* docs: update README.md

* docs: gave README.md a once over, and made a few edits
  • Loading branch information
bcoe authored Jul 7, 2016
1 parent f67bff7 commit e45b51b
Show file tree
Hide file tree
Showing 6 changed files with 268 additions and 39 deletions.
33 changes: 21 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,12 @@
[![Windows Tests](https://img.shields.io/appveyor/ci/bcoe/nyc-ilw23/master.svg?label=Windows%20Tests)](https://ci.appveyor.com/project/bcoe/nyc-ilw23)
[![Standard Version](https://img.shields.io/badge/release-standard%20version-brightgreen.svg)](https://github.com/conventional-changelog/standard-version)

Istanbul's high-tech command line interface, with support for:
Istanbul's state of the art command line interface, with support for:

* applications that spawn subprocesses.
* ES2015 transforms, via [babel-plugin-istanbul](https://github.com/istanbuljs/babel-plugin-istanbul), or source-maps.

## Instrumenting Your Code
## Instrumenting your code

You can install nyc as a development dependency and add it to the test stanza
in your package.json.
Expand Down Expand Up @@ -50,10 +50,10 @@ and a `text-lcov` coverage report.
nyc --reporter=lcov --reporter=text-lcov npm test
```

## Support For Custom Require Hooks (Babel! ES2015!)
## Support for custom require hooks (babel, webpack, etc.)

nyc supports custom require hooks like
[`babel-register`](http://babeljs.io/docs/usage/require/). If necessary nyc can
[`babel-register`](http://babeljs.io/docs/usage/require/). nyc can
load the hooks for you, [using the `--require`
flag](#require-additional-modules).

Expand All @@ -62,7 +62,7 @@ of the pre-transpiled code. You'll have to configure your custom require hook
to inline the source map in the transpiled code. For Babel that means setting
the `sourceMaps` option to `inline`.

## Use with babel-plugin-istanbul for Better ES6/ES7 Support
## Use with babel-plugin-istanbul for ES6/ES7/ES2015 Support

[`babel-plugin-istanbul`](https://github.com/istanbuljs/babel-plugin-istanbul) can be used to enable better first-class ES6 support.

Expand Down Expand Up @@ -107,7 +107,7 @@ That's all there is to it, better ES6 syntax highlighting awaits:

<img width="500" src="screen2.png">

## Support For Custom File Extensions (.jsx, .es6)
## Support for alternate file extensions (.jsx, .es6)

Supporting file extensions can be configured through either the configuration arguments or with the `nyc` config section in `package.json`.

Expand All @@ -126,7 +126,7 @@ nyc --extension .jsx --extension .es6 npm test
}
```

## Checking Coverage
## Checking coverage

nyc can fail tests if coverage falls below a threshold.
After running your tests with nyc, simply run:
Expand All @@ -144,7 +144,7 @@ nyc --check-coverage --lines 100 npm test

The above check fails if coverage falls below 100%.

## Running Reports
## Running reports

Once you've run your tests with nyc, simply run:

Expand All @@ -162,7 +162,7 @@ you can use any reporters that are supported by istanbul:
nyc report --reporter=lcov
```

## Excluding Files
## Excluding files

You can tell nyc to exclude specific files and directories by adding
an `nyc.exclude` array to your `package.json`. Each element of
Expand Down Expand Up @@ -192,7 +192,7 @@ directory:
which would exclude `test`/`__tests__` directories as well as `test.js`, `*.test.js`,
and `test-*.js` files. Specifying your own exclude property overrides these defaults.

## Including Files
## Including files

As an alternative to providing a list of files to `exclude`, you can provide
an `include` key to specify specific files that should be covered:
Expand All @@ -207,7 +207,7 @@ an `include` key to specify specific files that should be covered:

> Note: include defaults to `['**']`
## Include Reports For Files That Are Not Required
## Include reports for files that are not required

By default nyc does not collect coverage for files that have not
been required, run nyc with the flag `--all` to enable this.
Expand Down Expand Up @@ -240,7 +240,16 @@ can also be specified in the `nyc` stanza of your package.json:
}
```

## Integrating With Coveralls
## Instrumenting source files

nyc's `instrument` command can be used to instrument
source files outside of the context of your unit-tests:

__instrument the entire ./lib folder:__

`nyc instrument ./lib ./output`

## Integrating with coveralls

[coveralls.io](https://coveralls.io) is a great tool for adding
coverage reports to your GitHub project. Here's how to get nyc
Expand Down
52 changes: 37 additions & 15 deletions bin/nyc.js
Original file line number Diff line number Diff line change
Expand Up @@ -16,23 +16,47 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
.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'
})
.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?'
})
.example('$0 check-coverage --lines 95', "check whether the JSON in nyc's output folder meets the thresholds provided")
})
.command(require('../lib/commands/instrument'))
.option('reporter', {
alias: 'r',
describe: 'coverage reporter(s) to use',
default: 'text',
global: true
default: 'text'
})
.option('report-dir', {
describe: 'default directory to output coverage reports in',
default: 'coverage',
global: true
describe: 'directory to output coverage reports in',
default: 'coverage'
})
.option('silent', {
alias: 's',
Expand Down Expand Up @@ -67,7 +91,7 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
type: 'boolean',
describe: 'cache instrumentation results for improved performance'
})
.options('extension', {
.option('extension', {
alias: 'e',
default: [],
describe: 'a list of extensions that nyc should handle in addition to .js'
Expand All @@ -79,23 +103,19 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
})
.option('branches', {
default: 0,
description: 'what % of branches must be covered?',
global: true
description: 'what % of branches must be covered?'
})
.option('functions', {
default: 0,
description: 'what % of functions must be covered?',
global: true
description: 'what % of functions must be covered?'
})
.option('lines', {
default: 90,
description: 'what % of lines must be covered?',
global: true
description: 'what % of lines must be covered?'
})
.option('statements', {
default: 0,
description: 'what % of statements must be covered?',
global: true
description: 'what % of statements must be covered?'
})
.option('source-map', {
default: true,
Expand All @@ -112,7 +132,7 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
.version()
.pkgConf('nyc', process.cwd())
.example('$0 npm test', 'instrument your tests with coverage')
.example('$0 --require --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 babel')
.example('$0 report --reporter=text-lcov', 'output lcov report after running your tests')
.epilog('visit https://git.io/voHar for list of available reporters')

Expand All @@ -125,6 +145,8 @@ if (argv._[0] === 'report') {
report(argv)
} else if (argv._[0] === 'check-coverage') {
checkCoverage(argv)
} else if (argv._[0] === 'instrument') {
// noop, let the command handler do its thing.
} else if (argv._.length) {
// wrap subprocesses and execute argv[1]
argv.require = arrify(argv.require)
Expand Down
67 changes: 58 additions & 9 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,18 +157,10 @@ NYC.prototype.addAllFiles = function () {

this._loadAdditionalModules()

var pattern = null
if (this.extensions.length === 1) {
pattern = '**/*' + this.extensions[0]
} else {
pattern = '**/*{' + this.extensions.join() + '}'
}

this.fakeRequire = true
glob.sync(pattern, {cwd: this.cwd, nodir: true, ignore: this.exclude.exclude}).forEach(function (filename) {
this.walkAllFiles(this.cwd, function (filename) {
filename = path.resolve(_this.cwd, filename)
_this.addFile(filename)

var coverage = coverageFinder()
var lastCoverage = _this.instrumenter().lastFileCoverage()
if (lastCoverage && _this.exclude.shouldInstrument(filename)) {
Expand All @@ -180,6 +172,63 @@ NYC.prototype.addAllFiles = function () {
this.writeCoverageFile()
}

NYC.prototype.instrumentAllFiles = function (input, output, cb) {
var _this = this
var inputDir = '.' + path.sep
var visitor = function (filename) {
var ext
var transform
var inFile = path.relative(_this.cwd, path.resolve(inputDir, filename))
var code = fs.readFileSync(inFile, 'utf-8')

for (ext in _this.transforms) {
if (filename.toLowerCase().substr(-ext.length) === ext) {
transform = _this.transforms[ext]
break
}
}

if (transform) {
code = transform(code, {filename: filename, relFile: inFile})
}

if (!output) {
console.log(code)
} else {
var outFile = path.relative(_this.cwd, path.resolve(output, filename))
mkdirp.sync(path.dirname(outFile))
fs.writeFileSync(outFile, code, 'utf-8')
}
}

this._loadAdditionalModules()

try {
var stats = fs.lstatSync(input)
if (stats.isDirectory()) {
inputDir = input
this.walkAllFiles(input, visitor)
} else {
visitor(input)
}
} catch (err) {
return cb(err)
}
}

NYC.prototype.walkAllFiles = function (dir, visitor) {
var pattern = null
if (this.extensions.length === 1) {
pattern = '**/*' + this.extensions[0]
} else {
pattern = '**/*{' + this.extensions.join() + '}'
}

glob.sync(pattern, {cwd: dir, nodir: true, ignore: this.exclude.exclude}).forEach(function (filename) {
visitor(filename)
})
}

NYC.prototype._maybeInstrumentSource = function (code, filename, relFile) {
var instrument = this.exclude.shouldInstrument(filename, relFile)
if (!instrument) {
Expand Down
55 changes: 55 additions & 0 deletions lib/commands/instrument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
var NYC
try {
NYC = require('../../index.covered.js')
} catch (e) {
NYC = require('../../index.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
.usage('$0 instrument <input> [output]')
.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: [],
describe: 'a list of extensions that nyc should handle in addition to .js'
})
.option('source-map', {
default: true,
type: 'boolean',
description: 'should nyc detect and handle source maps?'
})
.option('instrument', {
default: true,
type: 'boolean',
description: 'should nyc handle instrumentation?'
})
.example('$0 instrument ./lib ./output', 'instrument all .js files in ./lib with coverage and output in ./output')
}

exports.handler = function (argv) {
// if instrument is set to false,
// enable a noop instrumenter.
if (!argv.instrument) argv.instrumenter = './lib/instrumenters/noop'
else argv.instrumenter = './lib/instrumenters/istanbul'

var nyc = new NYC({
instrumenter: argv.instrumenter,
sourceMap: argv.sourceMap,
extension: argv.extension,
require: argv.require
})

nyc.instrumentAllFiles(argv.input, argv.output, function (err) {
if (err) console.error(err.message)
process.exit(1)
})
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -84,13 +84,13 @@
"istanbul-lib-instrument": "^1.1.0-alpha.1",
"istanbul-lib-report": "^1.0.0-alpha.3",
"istanbul-lib-source-maps": "^1.0.0-alpha.10",
"istanbul-reports": "^1.0.0-alpha.6",
"istanbul-reports": "^1.0.0-alpha.7",
"md5-hex": "^1.2.0",
"micromatch": "^2.3.7",
"mkdirp": "^0.5.0",
"pkg-up": "^1.0.0",
"resolve-from": "^2.0.0",
"rimraf": "^2.5.0",
"rimraf": "^2.5.3",
"signal-exit": "^3.0.0",
"spawn-wrap": "^1.2.2",
"test-exclude": "^1.1.0",
Expand Down Expand Up @@ -147,4 +147,4 @@
"test-exclude",
"yargs"
]
}
}
Loading

0 comments on commit e45b51b

Please sign in to comment.