Skip to content

Commit 9fd2295

Browse files
authored
feat: make __coverage__ the default approach we advocate for ES2015 coverage (#268)
* feat: make nyc behave better when used with babel-plugin-__coverage__ (see #266) * feat: abstract test include/exclude logic into its own module * fix: add text-exclude dependency * fix: should have instrumenters in files list * fix: fix remapping issue with source-maps * fix: typo was causing argument not to propagate * docs: add section introducing __coverage__ for ES6/ES7 * docs: a couple small edits
1 parent 8164b5e commit 9fd2295

File tree

12 files changed

+213
-156
lines changed

12 files changed

+213
-156
lines changed

README.md

+36-2
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,42 @@ of the pre-transpiled code. You'll have to configure your custom require hook
6060
to inline the source map in the transpiled code. For Babel that means setting
6161
the `sourceMaps` option to `inline`.
6262

63+
## Use babel-plugin__coverage__ for Better ES6/ES7 Support
64+
65+
[`babel-plugin-__coverage__`](https://github.com/dtinth/babel-plugin-__coverage__) can be used to enable better first-class ES6 support.
66+
67+
1. enable the `__coverage__` plugin:
68+
69+
```json
70+
{
71+
"babel": {
72+
"presets": ["es2015"],
73+
"plugins": ["__coverage__"]
74+
}
75+
}
76+
```
77+
78+
2. disable nyc's instrumentation and source-maps:
79+
80+
```json
81+
{
82+
"nyc": {
83+
"include": [
84+
"src/*.js"
85+
],
86+
"require": [
87+
"babel-register"
88+
],
89+
"sourceMap": false,
90+
"instrumenter": "./lib/instrumenters/noop"
91+
}
92+
}
93+
```
94+
95+
That's all there is to it, better ES6 syntax highlighting awaits:
96+
97+
<img width="500" src="screen2.png">
98+
6399
## Support For Custom File Extensions (.jsx, .es6)
64100

65101
Supporting file extensions can be configured through either the configuration arguments or with the `nyc` config section in `package.json`.
@@ -249,8 +285,6 @@ That's all there is to it!
249285
[codecov](https://codecov.io/) is a great tool for adding
250286
coverage reports to your GitHub project, even viewing them inline on GitHub with a browser extension:
251287

252-
![browser extension](https://d234q63orb21db.cloudfront.net/ad63907877249140772dff929ad1c340e424962a/media/images/next/extension.png)
253-
254288
Here's how to get `nyc` integrated with codecov and travis-ci.org:
255289

256290
1. add the codecov and nyc dependencies to your module:

bin/nyc.js

+15-2
Original file line numberDiff line numberDiff line change
@@ -97,6 +97,16 @@ var yargs = require('yargs/yargs')(process.argv.slice(2))
9797
description: 'what % of statements must be covered?',
9898
global: true
9999
})
100+
.option('source-map', {
101+
default: true,
102+
type: 'boolean',
103+
description: 'should nyc detect and handle source maps?'
104+
})
105+
.option('instrumenter', {
106+
default: './lib/instrumenters/istanbul',
107+
type: 'string',
108+
description: 'what library should be used to instrument coverage?'
109+
})
100110
.help('h')
101111
.alias('h', 'help')
102112
.version()
@@ -125,15 +135,18 @@ if (argv._[0] === 'report') {
125135
var nyc = (new NYC({
126136
require: argv.require,
127137
include: argv.include,
128-
exclude: argv.exclude
138+
exclude: argv.exclude,
139+
sourceMap: !!argv.sourceMap
129140
}))
130141
nyc.reset()
131142

132143
if (argv.all) nyc.addAllFiles()
133144

134145
var env = {
135146
NYC_CWD: process.cwd(),
136-
NYC_CACHE: argv.cache ? 'enable' : 'disable'
147+
NYC_CACHE: argv.cache ? 'enable' : 'disable',
148+
NYC_SOURCE_MAP: argv.sourceMap ? 'enable' : 'disable',
149+
NYC_INSTRUMENTER: argv.instrumenter
137150
}
138151
if (argv.require.length) {
139152
env.NYC_REQUIRE = argv.require.join(',')

bin/wrap.js

+3-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ try {
1111
extension: process.env.NYC_EXTENSION ? process.env.NYC_EXTENSION.split(',') : [],
1212
exclude: process.env.NYC_EXCLUDE ? process.env.NYC_EXCLUDE.split(',') : [],
1313
include: process.env.NYC_INCLUDE ? process.env.NYC_INCLUDE.split(',') : [],
14-
enableCache: process.env.NYC_CACHE === 'enable'
14+
enableCache: process.env.NYC_CACHE === 'enable',
15+
sourceMap: process.env.NYC_SOURCE_MAP === 'enable',
16+
instrumenter: process.env.NYC_INSTRUMENTER
1517
})).wrap()
1618

1719
sw.runMain()

index.js

+26-72
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
/* global __coverage__ */
22
var fs = require('fs')
33
var glob = require('glob')
4-
var micromatch = require('micromatch')
54
var mkdirp = require('mkdirp')
65
var Module = require('module')
76
var appendTransform = require('append-transform')
@@ -17,7 +16,8 @@ var md5hex = require('md5-hex')
1716
var findCacheDir = require('find-cache-dir')
1817
var js = require('default-require-extensions/js')
1918
var pkgUp = require('pkg-up')
20-
var yargs = require('yargs/yargs')
19+
var testExclude = require('test-exclude')
20+
var yargs = require('yargs')
2121

2222
/* istanbul ignore next */
2323
if (/index\.covered\.js$/.test(__filename)) {
@@ -30,29 +30,23 @@ function NYC (opts) {
3030
this._istanbul = config.istanbul
3131
this.subprocessBin = config.subprocessBin || path.resolve(__dirname, './bin/nyc.js')
3232
this._tempDirectory = config.tempDirectory || './.nyc_output'
33+
this._instrumenterLib = require(config.instrumenter || './lib/instrumenters/istanbul')
3334
this._reportDir = config.reportDir
35+
this._sourceMap = config.sourceMap
3436
this.cwd = config.cwd
3537

3638
this.reporter = arrify(config.reporter || 'text')
3739

38-
// load exclude stanza from config.
39-
this.include = false
40-
if (config.include && config.include.length > 0) {
41-
this.include = this._prepGlobPatterns(arrify(config.include))
42-
}
43-
44-
this.exclude = this._prepGlobPatterns(
45-
['**/node_modules/**'].concat(arrify(
46-
config.exclude && config.exclude.length > 0
47-
? config.exclude
48-
: ['test/**', 'test{,-*}.js', '**/*.test.js', '**/__tests__/**']
49-
))
50-
)
51-
5240
this.cacheDirectory = findCacheDir({name: 'nyc', cwd: this.cwd})
5341

5442
this.enableCache = Boolean(this.cacheDirectory && (config.enableCache === true || process.env.NYC_CACHE === 'enable'))
5543

44+
this.exclude = testExclude({
45+
cwd: this.cwd,
46+
include: config.include,
47+
exclude: config.exclude
48+
})
49+
5650
// require extensions can be provided as config in package.json.
5751
this.require = arrify(config.require)
5852

@@ -129,43 +123,7 @@ NYC.prototype.instrumenter = function () {
129123
}
130124

131125
NYC.prototype._createInstrumenter = function () {
132-
var configFile = path.resolve(this.cwd, './.istanbul.yml')
133-
134-
if (!fs.existsSync(configFile)) configFile = undefined
135-
136-
var istanbul = this.istanbul()
137-
138-
var instrumenterConfig = istanbul.config.loadFile(configFile).instrumentation.config
139-
140-
return new istanbul.Instrumenter({
141-
coverageVariable: '__coverage__',
142-
embedSource: instrumenterConfig['embed-source'],
143-
noCompact: !instrumenterConfig.compact,
144-
preserveComments: instrumenterConfig['preserve-comments']
145-
})
146-
}
147-
148-
NYC.prototype._prepGlobPatterns = function (patterns) {
149-
if (!patterns) return patterns
150-
151-
var result = []
152-
153-
function add (pattern) {
154-
if (result.indexOf(pattern) === -1) {
155-
result.push(pattern)
156-
}
157-
}
158-
159-
patterns.forEach(function (pattern) {
160-
// Allow gitignore style of directory exclusion
161-
if (!/\/\*\*$/.test(pattern)) {
162-
add(pattern.replace(/\/$/, '') + '/**')
163-
}
164-
165-
add(pattern)
166-
})
167-
168-
return result
126+
return this._instrumenterLib(this.cwd)
169127
}
170128

171129
NYC.prototype.addFile = function (filename) {
@@ -190,14 +148,6 @@ NYC.prototype._readTranspiledSource = function (path) {
190148
return source
191149
}
192150

193-
NYC.prototype.shouldInstrumentFile = function (filename, relFile) {
194-
// Don't instrument files that are outside of the current working directory.
195-
if (/^\.\./.test(path.relative(this.cwd, filename))) return false
196-
197-
relFile = relFile.replace(/^\.[\\\/]/, '') // remove leading './' or '.\'.
198-
return (!this.include || micromatch.any(relFile, this.include)) && !micromatch.any(relFile, this.exclude)
199-
}
200-
201151
NYC.prototype.addAllFiles = function () {
202152
var _this = this
203153

@@ -210,7 +160,7 @@ NYC.prototype.addAllFiles = function () {
210160
pattern = '**/*{' + this.extensions.join() + '}'
211161
}
212162

213-
glob.sync(pattern, {cwd: this.cwd, nodir: true, ignore: this.exclude}).forEach(function (filename) {
163+
glob.sync(pattern, {cwd: this.cwd, nodir: true, ignore: this.exclude.exclude}).forEach(function (filename) {
214164
var obj = _this.addFile(path.join(_this.cwd, filename))
215165
if (obj.instrument) {
216166
module._compile(
@@ -224,7 +174,7 @@ NYC.prototype.addAllFiles = function () {
224174
}
225175

226176
NYC.prototype._maybeInstrumentSource = function (code, filename, relFile) {
227-
var instrument = this.shouldInstrumentFile(filename, relFile)
177+
var instrument = this.exclude.shouldInstrument(filename, relFile)
228178

229179
if (!instrument) {
230180
return null
@@ -248,20 +198,24 @@ NYC.prototype._transformFactory = function (cacheDir) {
248198
return function (code, metadata, hash) {
249199
var filename = metadata.filename
250200

251-
var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename))
252-
if (sourceMap) {
253-
if (hash) {
254-
var mapPath = path.join(cacheDir, hash + '.map')
255-
fs.writeFileSync(mapPath, sourceMap.toJSON())
256-
} else {
257-
_this.sourceMapCache.addMap(filename, sourceMap.toJSON())
258-
}
259-
}
201+
if (_this._sourceMap) _this._handleSourceMap(cacheDir, code, hash, filename)
260202

261203
return instrumenter.instrumentSync(code, filename)
262204
}
263205
}
264206

207+
NYC.prototype._handleSourceMap = function (cacheDir, code, hash, filename) {
208+
var sourceMap = convertSourceMap.fromSource(code) || convertSourceMap.fromMapFileSource(code, path.dirname(filename))
209+
if (sourceMap) {
210+
if (hash) {
211+
var mapPath = path.join(cacheDir, hash + '.map')
212+
fs.writeFileSync(mapPath, sourceMap.toJSON())
213+
} else {
214+
this.sourceMapCache.addMap(filename, sourceMap.toJSON())
215+
}
216+
}
217+
}
218+
265219
NYC.prototype._handleJs = function (code, filename) {
266220
var relFile = path.relative(this.cwd, filename)
267221
return this._maybeInstrumentSource(code, filename, relFile) || code

lib/instrumenters/istanbul.js

+23
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
var fs = require('fs')
2+
var path = require('path')
3+
4+
function InstrumenterIstanbul (cwd) {
5+
var configFile = path.resolve(cwd, './.istanbul.yml')
6+
7+
if (!fs.existsSync(configFile)) configFile = undefined
8+
var istanbul = InstrumenterIstanbul.istanbul()
9+
var instrumenterConfig = istanbul.config.loadFile(configFile).instrumentation.config
10+
11+
return new istanbul.Instrumenter({
12+
coverageVariable: '__coverage__',
13+
embedSource: instrumenterConfig['embed-source'],
14+
noCompact: !instrumenterConfig.compact,
15+
preserveComments: instrumenterConfig['preserve-comments']
16+
})
17+
}
18+
19+
InstrumenterIstanbul.istanbul = function () {
20+
return InstrumenterIstanbul._istanbul || (InstrumenterIstanbul._istanbul = require('istanbul'))
21+
}
22+
23+
module.exports = InstrumenterIstanbul

lib/instrumenters/noop.js

+9
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
function NOOP () {
2+
return {
3+
instrumentSync: function (code) {
4+
return code
5+
}
6+
}
7+
}
8+
9+
module.exports = NOOP

lib/source-map-cache.js

+5
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,11 @@ SourceMapCache.prototype._rewriteBranches = function (fileReport, sourceMap) {
165165

166166
if (locations.length > 0) {
167167
b[index + ''] = fileReport.b[k]
168+
169+
/* istanbul ignore next: hard to test for edge-case,
170+
counts for more statements than exist post remapping */
171+
while (b[index + ''].length > locations.length) b[index + ''].pop()
172+
168173
branchMap[index + ''] = {
169174
line: locations[0].start.line,
170175
type: item.type,

package.json

+3
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
"index.js",
2323
"bin/*.js",
2424
"lib/*.js",
25+
"lib/instrumenters/*.js",
2526
"!**/*covered.js"
2627
],
2728
"nyc": {
@@ -85,6 +86,7 @@
8586
"signal-exit": "^2.1.1",
8687
"source-map": "^0.5.3",
8788
"spawn-wrap": "^1.2.2",
89+
"test-exclude": "^1.1.0",
8890
"yargs": "^4.7.0"
8991
},
9092
"devDependencies": {
@@ -131,6 +133,7 @@
131133
"signal-exit",
132134
"source-map",
133135
"spawn-wrap",
136+
"test-exclude",
134137
"yargs"
135138
]
136139
}

screen2.png

62.2 KB
Loading

test/fixtures/cli/env.js

+1
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
console.log(JSON.stringify(process.env))

test/src/nyc-bin.js

+42
Original file line numberDiff line numberDiff line change
@@ -197,4 +197,46 @@ describe('the nyc cli', function () {
197197
})
198198
})
199199
})
200+
201+
it('passes configuration via environment variables', function (done) {
202+
var args = [
203+
bin,
204+
'--silent',
205+
'--require=mkdirp',
206+
'--include=env.js',
207+
'--exclude=batman.js',
208+
'--extension=.js',
209+
'--cache=true',
210+
'--source-map=true',
211+
'--instrumenter=./lib/instrumenters/istanbul.js',
212+
process.execPath,
213+
'./env.js'
214+
]
215+
var expected = {
216+
NYC_REQUIRE: 'mkdirp',
217+
NYC_INCLUDE: 'env.js',
218+
NYC_EXCLUDE: 'batman.js',
219+
NYC_EXTENSION: '.js',
220+
NYC_CACHE: 'enable',
221+
NYC_SOURCE_MAP: 'enable',
222+
NYC_INSTRUMENTER: './lib/instrumenters/istanbul.js'
223+
}
224+
225+
var proc = spawn(process.execPath, args, {
226+
cwd: fixturesCLI,
227+
env: env
228+
})
229+
230+
var stdout = ''
231+
proc.stdout.on('data', function (chunk) {
232+
stdout += chunk
233+
})
234+
235+
proc.on('close', function (code) {
236+
code.should.equal(0)
237+
var env = JSON.parse(stdout)
238+
env.should.include(expected)
239+
done()
240+
})
241+
})
200242
})

0 commit comments

Comments
 (0)