Skip to content

Commit

Permalink
Merge pull request #84 from bcoe/improve-exclude
Browse files Browse the repository at this point in the history
glob-based include/exclude functionality
  • Loading branch information
bcoe committed Dec 9, 2015
2 parents 08bb1c9 + 55c4202 commit 40bb454
Show file tree
Hide file tree
Showing 6 changed files with 160 additions and 27 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,9 @@

### Upcoming

- [#84](https://github.com/bcoe/nyc/pull/84) glob based include/exlude of files (@Lalem001)
- [#78](https://github.com/bcoe/nyc/pull/78) improvements to sourcemap tests (@novemberborn)
- [#73](https://github.com/bcoe/nyc/pull/73) improvements to require tests (@novemberborn)
- [#65](https://github.com/bcoe/nyc/pull/65) significant improvements to require hooks (@novemberborn)
- [#64](https://github.com/bcoe/nyc/pull/64) upgrade Istanbul (@novemberborn)

Expand Down
43 changes: 35 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -93,20 +93,48 @@ nyc report --reporter=lcov

## Excluding Files

By default nyc does not instrument files in `node_modules`, or `test`
for coverage. You can override this setting in your package.json, by
adding the following configuration:
You can tell nyc to exclude specific files and directories by adding
an `config.nyc.exclude` array to your `package.json`. Each element of
the array is a glob pattern indicating which paths should be omitted.

```js
Globs are matched using [micromatch](https://www.npmjs.com/package/micromatch)

In addition to patterns specified in the package, nyc will always exclude
files in `node_modules`.

For example, the following config will exclude all `node_modules`,
any files with the extension `.spec.js`, and anything in the `build`
directory:

```json
{"config": {
"nyc": {
"exclude": [
"node_modules/"
"**/*.spec.js",
"build"
]
}
}}
```

> Note: exclude defaults to `['test', 'test{,-*}.js']`, which would exclude
the `test` directory as well as `test.js` and `test-*.js` 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:

```json
{"config": {
"nyc": {
"include": ["**/build/umd/moment.js"]
}
}}
```

> Note: include defaults to `['**']`
## Include Reports For Files That Are Not Required

By default nyc does not collect coverage for files that have not
Expand Down Expand Up @@ -148,7 +176,7 @@ npm install coveralls nyc --save
{
"script": {
"test": "nyc tap ./test/*.js",
"coverage": "nyc report --reporter=text-lcov | coveralls",
"coverage": "nyc npm test && nyc report --reporter=text-lcov | coveralls",
}
}
```
Expand All @@ -164,5 +192,4 @@ after_success: npm run coverage

That's all there is to it!

_Note: by default coveralls.io adds comments to pull-requests on GitHub, this can
feel intrusive. To disable this, click on your repo on coveralls.io and uncheck `LEAVE COMMENTS?`._
> Note: by default coveralls.io adds comments to pull-requests on GitHub, this can feel intrusive. To disable this, click on your repo on coveralls.io and uncheck `LEAVE COMMENTS?`._
44 changes: 29 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
var _ = require('lodash')
var fs = require('fs')
var glob = require('glob')
var micromatch = require('micromatch')
var mkdirp = require('mkdirp')
var Module = require('module')
var path = require('path')
Expand Down Expand Up @@ -31,11 +32,12 @@ function NYC (opts) {
config = config.nyc || {}

// load exclude stanza from config.
this.exclude = config.exclude || ['node_modules[\/\\\\]', 'test[\/\\\\]', 'test\\.js']
this.include = config.include || ['**']
this.include = this._prepGlobPatterns(this.include)

this.exclude = ['**/node_modules/**'].concat(config.exclude || ['test/**', 'test{,-*}.js'])
if (!Array.isArray(this.exclude)) this.exclude = [this.exclude]
this.exclude = _.map(this.exclude, function (p) {
return new RegExp(p)
})
this.exclude = this._prepGlobPatterns(this.exclude)

// require extensions can be provided as config in package.json.
this.require = config.require ? config.require : this.require
Expand Down Expand Up @@ -73,9 +75,24 @@ NYC.prototype._createInstrumenter = function () {
})
}

NYC.prototype._prepGlobPatterns = function (patterns) {
if (!patterns) return patterns

var directories = []
patterns = _.map(patterns, function (pattern) {
// Allow gitignore style of directory exclusion
if (!_.endsWith(pattern, '/**')) {
directories.push(pattern.replace(/\/$/, '').concat('/**'))
}

return pattern
})
return _.union(patterns, directories)
}

NYC.prototype.addFile = function (filename, returnImmediately) {
var relFile = path.relative(this.cwd, filename)
var instrument = this.shouldInstrumentFile(relFile)
var instrument = this.shouldInstrumentFile(filename, relFile)
var content = stripBom(fs.readFileSync(filename, 'utf8'))

if (instrument) {
Expand All @@ -94,7 +111,7 @@ NYC.prototype.addFile = function (filename, returnImmediately) {

NYC.prototype.addContent = function (filename, content) {
var relFile = path.relative(this.cwd, filename)
var instrument = this.shouldInstrumentFile(relFile)
var instrument = this.shouldInstrumentFile(filename, relFile)

if (instrument) {
content = this.instrumenter.instrumentSync(content, './' + relFile)
Expand All @@ -107,22 +124,19 @@ NYC.prototype.addContent = function (filename, content) {
}
}

NYC.prototype.shouldInstrumentFile = function (relFile, returnImmediately) {
// only instrument a file if it's not on the exclude list.
for (var i = 0, exclude; (exclude = this.exclude[i]) !== undefined; i++) {
if (exclude.test(relFile)) {
return false
}
}
return true
NYC.prototype.shouldInstrumentFile = function (filename, relFile) {
relFile = relFile.replace(/^\.\//, '') // remove leading './'.

return (micromatch.any(filename, this.include) || micromatch.any(relFile, this.include)) &&
!(micromatch.any(filename, this.exclude) || micromatch.any(relFile, this.exclude))
}

NYC.prototype.addAllFiles = function () {
var _this = this

this._createOutputDirectory()

glob.sync('**/*.js', {nodir: true}).forEach(function (filename) {
glob.sync('**/*.js', {nodir: true, ignore: this.exclude}).forEach(function (filename) {
var obj = _this.addFile(filename, true)
if (obj.instrument) {
module._compile(
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
"node_modules",
"bin",
"coverage",
"test/fixtures/coverage.js",
"test/nyc-test.js",
"test/source-map-cache.js",
"test/fixtures/_generateCoverage.js"
Expand Down Expand Up @@ -51,6 +52,7 @@
"glob": "^5.0.14",
"istanbul": "^0.4.1",
"lodash": "^3.10.0",
"micromatch": "^2.1.6",
"mkdirp": "^0.5.0",
"rimraf": "^2.4.2",
"signal-exit": "^2.1.1",
Expand Down
5 changes: 2 additions & 3 deletions test/fixtures/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@
"config": {
"nyc": {
"exclude": [
"node_modules/",
"blarg/",
"blerg/"
"**/blarg",
"**/blerg"
]
}
}
Expand Down
90 changes: 89 additions & 1 deletion test/nyc-test.js
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,95 @@ describe('nyc', function () {
cwd: path.resolve(__dirname, './fixtures')
})

nyc.exclude.length.should.eql(3)
nyc.exclude.length.should.eql(5)
})
})

describe('_prepGlobPatterns', function () {
it('should adjust patterns appropriately', function () {
var _prepGlobPatterns = NYC.prototype._prepGlobPatterns

var result = _prepGlobPatterns(['./foo', 'bar/**', 'baz/'])

result.should.deep.equal([
'./foo',
'bar/**',
'baz/',
'./foo/**', // Appended `/**`
'baz/**' // Removed trailing slash before appending `/**`
])
})
})

describe('shouldInstrumentFile', function () {
it('should exclude appropriately with defaults', function () {
var nyc = new NYC()

// Root package contains config.exclude
// Restore exclude to default patterns
nyc.exclude = nyc._prepGlobPatterns([
'**/node_modules/**',
'test/**',
'test{,-*}.js'
])

var shouldInstrumentFile = nyc.shouldInstrumentFile.bind(nyc)

// nyc always excludes "node_modules/**"
shouldInstrumentFile('foo', 'foo').should.equal(true)
shouldInstrumentFile('node_modules/bar', 'node_modules/bar').should.equal(false)
shouldInstrumentFile('foo/node_modules/bar', 'foo/node_modules/bar').should.equal(false)
shouldInstrumentFile('test.js', 'test.js').should.equal(false)
shouldInstrumentFile('testfoo.js', 'testfoo.js').should.equal(true)
shouldInstrumentFile('test-foo.js', 'test-foo.js').should.equal(false)
shouldInstrumentFile('lib/test.js', 'lib/test.js').should.equal(true)
shouldInstrumentFile('/foo/bar/test.js', './test.js').should.equal(false)
})

it('should exclude appropriately with config.exclude', function () {
var nyc = new NYC({
cwd: fixtures
})
var shouldInstrumentFile = nyc.shouldInstrumentFile.bind(nyc)

// config.excludes: "blarg", "blerg"
shouldInstrumentFile('blarg', 'blarg').should.equal(false)
shouldInstrumentFile('blarg/foo.js', 'blarg/foo.js').should.equal(false)
shouldInstrumentFile('blerg', 'blerg').should.equal(false)
shouldInstrumentFile('./blerg', './blerg').should.equal(false)
})

it('should handle example symlinked node_module', function () {
var nyc = new NYC({
cwd: fixtures
})
var shouldInstrumentFile = nyc.shouldInstrumentFile.bind(nyc)

var relPath = '../../nyc/node_modules/glob/glob.js'
var fullPath = '/Users/user/nyc/node_modules/glob/glob.js'

shouldInstrumentFile(fullPath, relPath).should.equal(false)

// Full path should be excluded (node_modules)
shouldInstrumentFile(fullPath, relPath).should.equal(false)

// Send both relative and absolute path
// Results in exclusion (include = false)
shouldInstrumentFile(fullPath, relPath).should.equal(false)
})

it('allows a file to be included rather than excluded', function () {
var nyc = new NYC()

// Root package contains config.exclude
// Restore exclude to default patterns
nyc.include = nyc._prepGlobPatterns([
'test.js'
])

var shouldInstrumentFile = nyc.shouldInstrumentFile.bind(nyc)
shouldInstrumentFile('test.js', 'test.js').should.equal(true)
shouldInstrumentFile('index.js', 'index.js').should.equal(false)
})
})

Expand Down

0 comments on commit 40bb454

Please sign in to comment.