diff --git a/README.md b/README.md index 43f4485c9..08160b587 100644 --- a/README.md +++ b/README.md @@ -53,14 +53,14 @@ and a `text-lcov` coverage report. nyc --reporter=lcov --reporter=text-lcov npm test ``` -### Accurate stack traces using source maps +### Accurate stack traces using source-maps When `produce-source-map` is set to true, then the instrumented source files will include inline source maps for the instrumenter transform. When combined with [source-map-support](https://github.com/evanw/node-source-map-support), stack traces for instrumented code will reflect their original lines. -### Support for custom require hooks (babel, webpack, etc.) +### Support for custom require hooks (babel, typescript, etc.) nyc supports custom require hooks like [`babel-register`](http://babeljs.io/docs/usage/require/). nyc can @@ -69,9 +69,20 @@ flag](#require-additional-modules). Source maps are used to map coverage information back to the appropriate lines 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 +to inline the source-map in the transpiled code. For Babel that means setting the `sourceMaps` option to `inline`. +### Source-Map support for pre-instrumented codebases + +If you opt to pre-instrument your source-code (rather than using a just-in-time +transpiler like [`babel-register`](http://babeljs.io/docs/usage/require/)) +nyc supports both inline source-maps and `.map` files. + +_Important: If you are using nyc with a project that pre-instruments its code, +run nyc with the configuration option `--exclude-after-remap` set to `false`. +Otherwise nyc's reports will exclude any files that source-maps remap to folders +covered under exclude rules._ + ## Use with `babel-plugin-istanbul` for Babel Support We recommend using [`babel-plugin-istanbul`](https://github.com/istanbuljs/babel-plugin-istanbul) if your @@ -118,12 +129,12 @@ That's all there is to it, better ES2015+ syntax highlighting awaits: -## Support for alternate file extensions (.jsx, .es6) +## Support for alternate file extensions (.jsx, .mjs) Supporting file extensions can be configured through either the configuration arguments or with the `nyc` config section in `package.json`. ```shell -nyc --extension .jsx --extension .es6 npm test +nyc --extension .jsx --extension .mjs npm test ``` ```json @@ -131,7 +142,7 @@ nyc --extension .jsx --extension .es6 npm test "nyc": { "extension": [ ".jsx", - ".es6" + ".mjs" ] } } @@ -320,9 +331,18 @@ You can specify custom high and low watermarks in nyc's configuration: } ``` -## Other advanced features +## Parsing Hints (Ignoring Lines) -Take a look at http://istanbul.js.org/docs/advanced/ and please feel free to [contribute documentation](https://github.com/istanbuljs/istanbuljs.github.io/tree/development/content). +There may be some sections of your codebase that you wish to purposefully +exclude from coverage tracking, to do so you can use the following parsing +hints: + +* `/* istanbul ignore if */`: ignore the next if statement. +* `/* istanbul ignore else */`: ignore the else portion of an if statement. +* `/* istanbul ignore next */`: ignore the next _thing_ in the source-code ( + functions, if statements, classes, you name it). +* `/* istanbul ignore file */`: ignore an entire source-file (this should be + placed at the top of the file). ## Integrating with coveralls @@ -396,8 +416,13 @@ Here's how to get `nyc` integrated with codecov and travis-ci.org: That's all there is to it! ## Integrating with TAP formatters + Many testing frameworks (Mocha, Tape, Tap, etc.) can produce [TAP](https://en.wikipedia.org/wiki/Test_Anything_Protocol) output. [tap-nyc](https://github.com/MegaArman/tap-nyc) is a TAP formatter designed to look nice with nyc. ## More tutorials You can find more tutorials at http://istanbul.js.org/docs/tutorials + +## Other advanced features + +Take a look at http://istanbul.js.org/docs/advanced/ and please feel free to [contribute documentation](https://github.com/istanbuljs/istanbuljs.github.io/tree/development/content). diff --git a/index.js b/index.js index b31c1abcf..c9cdd8819 100755 --- a/index.js +++ b/index.js @@ -420,9 +420,15 @@ NYC.prototype._getCoverageMapFromAllCoverageFiles = function () { this.loadReports().forEach(function (report) { map.merge(report) }) - map.filter(function (filename) { - return _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(function (filename) { + return _this.exclude.shouldInstrument(filename) + }) + } map.data = this.sourceMaps.remapCoverage(map.data) return map } diff --git a/lib/config-util.js b/lib/config-util.js index 54c8804e6..89a396d1c 100644 --- a/lib/config-util.js +++ b/lib/config-util.js @@ -75,6 +75,12 @@ Config.buildYargs = function (cwd) { 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('include', { alias: 'n', default: [], diff --git a/package.json b/package.json index 6ba16e152..a837722f0 100644 --- a/package.json +++ b/package.json @@ -96,7 +96,7 @@ "resolve-from": "^2.0.0", "rimraf": "^2.5.4", "signal-exit": "^3.0.1", - "spawn-wrap": "^1.4.0", + "spawn-wrap": "1.3.8", "test-exclude": "^4.1.1", "yargs": "^10.0.3", "yargs-parser": "^8.0.0" @@ -119,6 +119,7 @@ "split-lines": "^1.0.0", "standard": "^9.0.2", "standard-version": "^4.0.0", + "strip-indent": "^2.0.0", "tap": "^10.0.0", "which": "^1.2.11", "zero-fill": "^2.2.3" diff --git a/test/fixtures/source-maps/instrumented/s1.min.js b/test/fixtures/source-maps/instrumented/s1.min.js new file mode 100644 index 000000000..bdc3399c1 --- /dev/null +++ b/test/fixtures/source-maps/instrumented/s1.min.js @@ -0,0 +1,2 @@ +var apple=99;var banana=200;function add(item1,item2){return item1+item2}function multiply(item1,item2){return item1*item2}add(apple,banana); +//# sourceMappingURL=s1.min.js.map diff --git a/test/fixtures/source-maps/instrumented/s1.min.js.map b/test/fixtures/source-maps/instrumented/s1.min.js.map new file mode 100644 index 000000000..f7cc9a29a --- /dev/null +++ b/test/fixtures/source-maps/instrumented/s1.min.js.map @@ -0,0 +1 @@ +{"version":3, "sourceRoot": "../", "sources":["original/s1.js"],"names":["apple","banana","add","item1","item2","multiply"],"mappings":"AAAA,IAAIA,MAAQ,GACZ,IAAIC,OAAS,IACb,SAASC,IAAKC,MAAOC,OACnB,OAAOD,MAAQC,MAEjB,SAASC,SAAUF,MAAOC,OACxB,OAAOD,MAAQC,MAEjBF,IAAIF,MAAOC"} diff --git a/test/fixtures/source-maps/instrumented/s2.min.js b/test/fixtures/source-maps/instrumented/s2.min.js new file mode 100644 index 000000000..818a9926d --- /dev/null +++ b/test/fixtures/source-maps/instrumented/s2.min.js @@ -0,0 +1,2 @@ +var strawberry=99;var pineapple=200;function add(item1,item2){return item1+item2}add(strawberry,pineapple); +//# sourceMappingURL=data:application/json;charset=utf-8;base64,eyJ2ZXJzaW9uIjozLCJzb3VyY2VzIjpbIm9yaWdpbmFsL3MyLmpzIl0sIm5hbWVzIjpbInN0cmF3YmVycnkiLCJwaW5lYXBwbGUiLCJhZGQiLCJpdGVtMSIsIml0ZW0yIl0sIm1hcHBpbmdzIjoiQUFBQSxJQUFJQSxXQUFhLEdBQ2pCLElBQUlDLFVBQVksSUFDaEIsU0FBU0MsSUFBS0MsTUFBT0MsT0FDbkIsT0FBT0QsTUFBUUMsTUFFakJGLElBQUlGLFdBQVlDIiwic291cmNlUm9vdCI6Ii4uLyJ9 \ No newline at end of file diff --git a/test/fixtures/source-maps/original/s1.js b/test/fixtures/source-maps/original/s1.js new file mode 100644 index 000000000..03dbd8a31 --- /dev/null +++ b/test/fixtures/source-maps/original/s1.js @@ -0,0 +1,9 @@ +var apple = 99 +var banana = 200 +function add (item1, item2) { + return item1 + item2 +} +function multiply (item1, item2) { + return item1 * item2 +} +add(apple, banana) diff --git a/test/fixtures/source-maps/original/s2.js b/test/fixtures/source-maps/original/s2.js new file mode 100644 index 000000000..372734f87 --- /dev/null +++ b/test/fixtures/source-maps/original/s2.js @@ -0,0 +1,6 @@ +var strawberry = 99 +var pineapple = 200 +function add (item1, item2) { + return item1 + item2 +} +add(strawberry, pineapple) diff --git a/test/fixtures/source-maps/package.json b/test/fixtures/source-maps/package.json new file mode 100644 index 000000000..0967ef424 --- /dev/null +++ b/test/fixtures/source-maps/package.json @@ -0,0 +1 @@ +{} diff --git a/test/nyc-bin.js b/test/nyc-bin.js index c2f817223..208762e73 100644 --- a/test/nyc-bin.js +++ b/test/nyc-bin.js @@ -1,16 +1,18 @@ /* global describe, it */ -var _ = require('lodash') -var path = require('path') -var bin = path.resolve(__dirname, '../bin/nyc') -var fixturesCLI = path.resolve(__dirname, './fixtures/cli') -var fakebin = path.resolve(fixturesCLI, 'fakebin') -var fixturesHooks = path.resolve(__dirname, './fixtures/hooks') -var fs = require('fs') -var glob = require('glob') -var isWindows = require('is-windows')() -var rimraf = require('rimraf') -var spawn = require('child_process').spawn +const _ = require('lodash') +const path = require('path') +const bin = path.resolve(__dirname, '../bin/nyc') +const fixturesCLI = path.resolve(__dirname, './fixtures/cli') +const fixturesHooks = path.resolve(__dirname, './fixtures/hooks') +const fixturesSourceMaps = path.resolve(__dirname, './fixtures/source-maps') +const fakebin = path.resolve(fixturesCLI, 'fakebin') +const fs = require('fs') +const glob = require('glob') +const isWindows = require('is-windows')() +const rimraf = require('rimraf') +const spawn = require('child_process').spawn +const si = require('strip-indent') require('chai').should() @@ -762,4 +764,120 @@ describe('the nyc cli', function () { done() }) }) + + // the following tests exercise nyc's behavior around source-maps + // that have been included with pre-instrumented files. Perhaps, as an + // example, unit tests are being run against minified JavaScript. + // --exclude-after-remap will likely need to be set to false when + // using nyc with this type of configuration. + describe('source-maps', () => { + describe('--all', () => { + it('includes files with both .map files and inline source-maps', (done) => { + const args = [ + bin, + '--all', + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s1.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|----------------| + File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | + ----------|----------|----------|----------|----------|----------------| + All files | 44.44 | 100 | 33.33 | 44.44 | | + s1.js | 80 | 100 | 50 | 80 | 7 | + s2.js | 0 | 100 | 0 | 0 | 1,2,4,6 | + ----------|----------|----------|----------|----------|----------------|` + ) + done() + }) + }) + }) + + describe('.map file', () => { + it('appropriately instruments file with corresponding .map file', (done) => { + const args = [ + bin, + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s1.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|----------------| + File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | + ----------|----------|----------|----------|----------|----------------| + All files | 80 | 100 | 50 | 80 | | + s1.js | 80 | 100 | 50 | 80 | 7 | + ----------|----------|----------|----------|----------|----------------|`) + done() + }) + }) + }) + + describe('inline', () => { + it('appropriately instruments a file with an inline source-map', (done) => { + const args = [ + bin, + '--cache', 'false', + '--exclude-after-remap', 'false', + '--exclude', 'original', + process.execPath, './instrumented/s2.min.js' + ] + + const proc = spawn(process.execPath, args, { + cwd: fixturesSourceMaps, + env: env + }) + + var stdout = '' + proc.stdout.on('data', function (chunk) { + stdout += chunk + }) + + proc.on('close', function (code) { + code.should.equal(0) + stdoutShouldEqual(stdout, ` + ----------|----------|----------|----------|----------|----------------| + File | % Stmts | % Branch | % Funcs | % Lines |Uncovered Lines | + ----------|----------|----------|----------|----------|----------------| + All files | 100 | 100 | 100 | 100 | | + s2.js | 100 | 100 | 100 | 100 | | + ----------|----------|----------|----------|----------|----------------|`) + done() + }) + }) + }) + }) }) + +function stdoutShouldEqual (stdout, expected) { + `\n${stdout}`.should.equal(`${si(expected)}\n`) +}