diff --git a/.gitignore b/.gitignore index 20cf3c43..e96120f7 100644 --- a/.gitignore +++ b/.gitignore @@ -3,3 +3,4 @@ node_modules .nyc_output coverage tmp +.idea \ No newline at end of file diff --git a/lib/commands/report.js b/lib/commands/report.js index cdc363b6..c02f4e94 100644 --- a/lib/commands/report.js +++ b/lib/commands/report.js @@ -19,7 +19,8 @@ exports.outputReport = async function (argv) { watermarks: argv.watermarks, resolve: argv.resolve, omitRelative: argv.omitRelative, - wrapperLength: argv.wrapperLength + wrapperLength: argv.wrapperLength, + all: argv.all }) await report.run() if (argv.checkCoverage) checkCoverages(argv, report) diff --git a/lib/parse-args.js b/lib/parse-args.js index 7be226ac..ee57b6c0 100644 --- a/lib/parse-args.js +++ b/lib/parse-args.js @@ -83,6 +83,12 @@ function buildYargs (withCommands = false) { type: 'boolean', describe: 'should temp files be deleted before script execution' }) + .options('all', { + default: false, + type: 'boolean', + describe: 'supplying --all will cause c8 to consider all src files in the current working directory ' + + 'when the determining coverage. Respects include/exclude.' + }) .pkgConf('c8') .config(config) .demandCommand(1) diff --git a/lib/report.js b/lib/report.js index 8aad24ff..0463c768 100644 --- a/lib/report.js +++ b/lib/report.js @@ -3,8 +3,8 @@ const furi = require('furi') const libCoverage = require('istanbul-lib-coverage') const libReport = require('istanbul-lib-report') const reports = require('istanbul-reports') -const { readdirSync, readFileSync } = require('fs') -const { isAbsolute, resolve } = require('path') +const { readdirSync, readFileSync, statSync } = require('fs') +const { isAbsolute, resolve, join, relative, extname, dirname } = require('path') // TODO: switch back to @c88/v8-coverage once patch is landed. const { mergeProcessCovs } = require('@bcoe/v8-coverage') const v8toIstanbul = require('v8-to-istanbul') @@ -20,7 +20,8 @@ class Report { watermarks, omitRelative, wrapperLength, - resolve: resolvePaths + resolve: resolvePaths, + all }) { this.reporter = reporter this.reportsDirectory = reportsDirectory @@ -34,6 +35,8 @@ class Report { this.omitRelative = omitRelative this.sourceMapCache = {} this.wrapperLength = wrapperLength + this.all = all + this.src = process.cwd() } async run () { @@ -57,8 +60,8 @@ class Report { // use-case. if (this._allCoverageFiles) return this._allCoverageFiles + const map = libCoverage.createCoverageMap() const v8ProcessCov = this._getMergedProcessCov() - const map = libCoverage.createCoverageMap({}) const resultCountPerPath = new Map() const possibleCjsEsmBridges = new Map() @@ -95,7 +98,6 @@ class Report { map.merge(converter.toIstanbul()) } } - this._allCoverageFiles = map return this._allCoverageFiles } @@ -128,6 +130,29 @@ class Report { return sources } + /** + * //TODO: use https://www.npmjs.com/package/convert-source-map + * // no need to roll this ourselves this is already in the dep tree + * https://sourcemaps.info/spec.html + * @param {String} compilation target file + * @returns {String} full path to source map file + * @private + */ + _getSourceMapFromFile (file) { + const fileBody = readFileSync(file).toString() + const sourceMapLineRE = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg + const results = fileBody.match(sourceMapLineRE) + if (results !== null) { + const sourceMap = results[results.length - 1].split('=')[1] + if (isAbsolute(sourceMap)) { + return sourceMap + } else { + const base = dirname(file) + return join(base, sourceMap) + } + } + } + /** * Returns the merged V8 process coverage. * @@ -139,14 +164,50 @@ class Report { */ _getMergedProcessCov () { const v8ProcessCovs = [] + const fileIndex = new Map() // Map for (const v8ProcessCov of this._loadReports()) { if (this._isCoverageObject(v8ProcessCov)) { if (v8ProcessCov['source-map-cache']) { Object.assign(this.sourceMapCache, v8ProcessCov['source-map-cache']) } - v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov)) + v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov, fileIndex)) } } + + if (this.all) { + const emptyReports = [] + v8ProcessCovs.unshift({ + result: emptyReports + }) + const workingDir = process.cwd() + this.exclude.globSync(workingDir).forEach((f) => { + const fullPath = resolve(workingDir, f) + if (!fileIndex.has(fullPath)) { + const ext = extname(f) + if (ext === '.js' || ext === '.ts' || ext === '.mjs') { + const stat = statSync(f) + const sourceMap = this._getSourceMapFromFile(f) + if (sourceMap !== undefined) { + this.sourceMapCache[`file://${fullPath}`] = { data: JSON.parse(readFileSync(sourceMap).toString()) } + } + emptyReports.push({ + scriptId: 0, + url: resolve(f), + functions: [{ + functionName: '(empty-report)', + ranges: [{ + startOffset: 0, + endOffset: stat.size, + count: 0 + }], + isBlockCoverage: true + }] + }) + } + } + }) + } + return mergeProcessCovs(v8ProcessCovs) } @@ -196,12 +257,13 @@ class Report { * @return {v8ProcessCov} Normalized V8 process coverage. * @private */ - _normalizeProcessCov (v8ProcessCov) { + _normalizeProcessCov (v8ProcessCov, fileIndex) { const result = [] for (const v8ScriptCov of v8ProcessCov.result) { if (/^file:\/\//.test(v8ScriptCov.url)) { try { v8ScriptCov.url = furi.toSysPath(v8ScriptCov.url) + fileIndex.set(v8ScriptCov.url, true) } catch (err) { console.warn(err) continue diff --git a/package-lock.json b/package-lock.json index 409bf7c3..ef377bcc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,8 +36,8 @@ }, "@types/istanbul-lib-coverage": { "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", - "integrity": "sha512-hRJD2ahnnpLgsj6KWMYSrmXkM3rm2Dl1qkx6IOFD5FnuNPXJIG5L0dhgKXCYTRMGzU4n0wImQ/xfmRc4POUFlg==" + "resolved": "http://artprod.dev.bloomberg.com/artifactory/api/npm/npm-repos/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.1.tgz", + "integrity": "sha1-QplbRG25pIoRoH7Ag0mahg6ROP8=" }, "JSONStream": { "version": "1.3.5", @@ -734,9 +734,9 @@ } }, "convert-source-map": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.6.0.tgz", - "integrity": "sha512-eFu7XigvxdZ1ETfbgPBohgyQ/Z++C0eEhTor0qRwBw9unw+L0/6V8wkSuGgzdThkiS5lSpdptOQPD8Ak40a+7A==", + "version": "1.7.0", + "resolved": "http://artprod.dev.bloomberg.com/artifactory/api/npm/npm-repos/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha1-F6LLiC1/d9NJBYXizmxSRCSjpEI=", "requires": { "safe-buffer": "~5.1.1" } @@ -4424,9 +4424,8 @@ "dev": true }, "v8-to-istanbul": { - "version": "3.2.6", - "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-3.2.6.tgz", - "integrity": "sha512-M6zzkVjsr+6sFdWPCuq7fjg9oCOXlssin05Yhobt9jMqHlEhw8AQ4/ClDiLCVWzXjpS2ezik53mhgSivw0XwmQ==", + "version": "git+https://github.com/istanbuljs/v8-to-istanbul.git#17325fc082e6e62902cd875f081c8355be850bd3", + "from": "git+https://github.com/istanbuljs/v8-to-istanbul.git#empty-report", "requires": { "@types/istanbul-lib-coverage": "^2.0.1", "convert-source-map": "^1.6.0", @@ -4435,8 +4434,8 @@ "dependencies": { "source-map": { "version": "0.7.3", - "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", - "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==" + "resolved": "http://artprod.dev.bloomberg.com/artifactory/api/npm/npm-repos/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha1-UwL4FpAxc1ImVECS5kmB91F1A4M=" } } }, diff --git a/package.json b/package.json index 91df177c..5ad86c0f 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,7 @@ "istanbul-reports": "^2.2.6", "rimraf": "^3.0.0", "test-exclude": "^5.2.3", - "v8-to-istanbul": "^3.2.6", + "v8-to-istanbul": "git+https://github.com/istanbuljs/v8-to-istanbul.git#empty-report", "yargs": "^14.0.0", "yargs-parser": "^15.0.0" }, diff --git a/test/fixtures/all/ts-compiled/dir/unloaded.js b/test/fixtures/all/ts-compiled/dir/unloaded.js new file mode 100644 index 00000000..097235bd --- /dev/null +++ b/test/fixtures/all/ts-compiled/dir/unloaded.js @@ -0,0 +1,8 @@ +"use strict"; +exports.__esModule = true; +function Unloaded() { + return 'Never loaded :('; +} +exports["default"] = Unloaded; +console.log("This file shouldn't have been evaluated"); +//# sourceMappingURL=unloaded.js.map \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/dir/unloaded.js.map b/test/fixtures/all/ts-compiled/dir/unloaded.js.map new file mode 100644 index 00000000..9f40d2ca --- /dev/null +++ b/test/fixtures/all/ts-compiled/dir/unloaded.js.map @@ -0,0 +1 @@ +{"version":3,"file":"unloaded.js","sourceRoot":"","sources":["unloaded.ts"],"names":[],"mappings":";;AAAA,SAAwB,QAAQ;IAC9B,OAAO,iBAAiB,CAAA;AAC1B,CAAC;AAFD,8BAEC;AAED,OAAO,CAAC,GAAG,CAAC,yCAAyC,CAAC,CAAA"} \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/dir/unloaded.ts b/test/fixtures/all/ts-compiled/dir/unloaded.ts new file mode 100644 index 00000000..c2a4e2ba --- /dev/null +++ b/test/fixtures/all/ts-compiled/dir/unloaded.ts @@ -0,0 +1,5 @@ +export default function Unloaded(){ + return 'Never loaded :(' +} + +console.log("This file shouldn't have been evaluated") \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/loaded.js b/test/fixtures/all/ts-compiled/loaded.js new file mode 100644 index 00000000..db8ceaac --- /dev/null +++ b/test/fixtures/all/ts-compiled/loaded.js @@ -0,0 +1,23 @@ +"use strict"; +exports.__esModule = true; +function getString(i) { + if (typeof i === 'number') { + if (isNaN(i)) { + return 'NaN'; + } + else if (i === 0) { + return 'zero'; + } + else if (i > 0) { + return 'positive'; + } + else { + return 'negative'; + } + } + else { + return 'wat?'; + } +} +exports["default"] = getString; +//# sourceMappingURL=loaded.js.map \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/loaded.js.map b/test/fixtures/all/ts-compiled/loaded.js.map new file mode 100644 index 00000000..3ce3fd1f --- /dev/null +++ b/test/fixtures/all/ts-compiled/loaded.js.map @@ -0,0 +1 @@ +{"version":3,"file":"loaded.js","sourceRoot":"","sources":["loaded.ts"],"names":[],"mappings":";;AAAA,SAAwB,SAAS,CAAC,CAAC;IACjC,IAAI,OAAO,CAAC,KAAK,QAAQ,EAAC;QACxB,IAAI,KAAK,CAAC,CAAC,CAAC,EAAC;YACX,OAAO,KAAK,CAAA;SACb;aACI,IAAI,CAAC,KAAK,CAAC,EAAC;YACf,OAAO,MAAM,CAAA;SACd;aACI,IAAI,CAAC,GAAG,CAAC,EAAC;YACb,OAAO,UAAU,CAAA;SAClB;aACI;YACH,OAAO,UAAU,CAAA;SAClB;KACF;SACI;QACH,OAAO,MAAM,CAAA;KACd;AACH,CAAC;AAlBD,+BAkBC"} \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/loaded.ts b/test/fixtures/all/ts-compiled/loaded.ts new file mode 100644 index 00000000..83f45893 --- /dev/null +++ b/test/fixtures/all/ts-compiled/loaded.ts @@ -0,0 +1,19 @@ +export default function getString(i){ + if (typeof i === 'number'){ + if (isNaN(i)){ + return 'NaN' + } + else if (i === 0){ + return 'zero' + } + else if (i > 0){ + return 'positive' + } + else { + return 'negative' + } + } + else { + return 'wat?' + } +} \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/main.js b/test/fixtures/all/ts-compiled/main.js new file mode 100644 index 00000000..2e0c0852 --- /dev/null +++ b/test/fixtures/all/ts-compiled/main.js @@ -0,0 +1,7 @@ +"use strict"; +exports.__esModule = true; +var loaded_1 = require("./loaded"); +console.log(loaded_1["default"](0)); +console.log(loaded_1["default"](1)); +console.log(loaded_1["default"](-1)); +//# sourceMappingURL=main.js.map \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/main.js.map b/test/fixtures/all/ts-compiled/main.js.map new file mode 100644 index 00000000..7e2b0cdb --- /dev/null +++ b/test/fixtures/all/ts-compiled/main.js.map @@ -0,0 +1 @@ +{"version":3,"file":"main.js","sourceRoot":"","sources":["main.ts"],"names":[],"mappings":";;AAAA,mCAAgC;AAChC,OAAO,CAAC,GAAG,CAAC,mBAAS,CAAC,CAAC,CAAC,CAAC,CAAA;AACzB,OAAO,CAAC,GAAG,CAAC,mBAAS,CAAC,CAAC,CAAC,CAAC,CAAA;AACzB,OAAO,CAAC,GAAG,CAAC,mBAAS,CAAC,CAAC,CAAC,CAAC,CAAC,CAAA"} \ No newline at end of file diff --git a/test/fixtures/all/ts-compiled/main.ts b/test/fixtures/all/ts-compiled/main.ts new file mode 100644 index 00000000..6677c2e0 --- /dev/null +++ b/test/fixtures/all/ts-compiled/main.ts @@ -0,0 +1,4 @@ +import getString from "./loaded" +console.log(getString(0)) +console.log(getString(1)) +console.log(getString(-1)) \ No newline at end of file diff --git a/test/fixtures/all/ts-only/dir/unloaded.ts b/test/fixtures/all/ts-only/dir/unloaded.ts new file mode 100644 index 00000000..c2a4e2ba --- /dev/null +++ b/test/fixtures/all/ts-only/dir/unloaded.ts @@ -0,0 +1,5 @@ +export default function Unloaded(){ + return 'Never loaded :(' +} + +console.log("This file shouldn't have been evaluated") \ No newline at end of file diff --git a/test/fixtures/all/ts-only/loaded.ts b/test/fixtures/all/ts-only/loaded.ts new file mode 100644 index 00000000..83f45893 --- /dev/null +++ b/test/fixtures/all/ts-only/loaded.ts @@ -0,0 +1,19 @@ +export default function getString(i){ + if (typeof i === 'number'){ + if (isNaN(i)){ + return 'NaN' + } + else if (i === 0){ + return 'zero' + } + else if (i > 0){ + return 'positive' + } + else { + return 'negative' + } + } + else { + return 'wat?' + } +} \ No newline at end of file diff --git a/test/fixtures/all/ts-only/main.ts b/test/fixtures/all/ts-only/main.ts new file mode 100644 index 00000000..6677c2e0 --- /dev/null +++ b/test/fixtures/all/ts-only/main.ts @@ -0,0 +1,4 @@ +import getString from "./loaded" +console.log(getString(0)) +console.log(getString(1)) +console.log(getString(-1)) \ No newline at end of file diff --git a/test/fixtures/all/vanilla/dir/unloaded.js b/test/fixtures/all/vanilla/dir/unloaded.js new file mode 100644 index 00000000..9baeca2e --- /dev/null +++ b/test/fixtures/all/vanilla/dir/unloaded.js @@ -0,0 +1,5 @@ +module.exports = function Unloaded(){ + return 'Never loaded :(' +} + +console.log("This file shouldn't have been evaluated") \ No newline at end of file diff --git a/test/fixtures/all/vanilla/loaded.js b/test/fixtures/all/vanilla/loaded.js new file mode 100644 index 00000000..ca9eb7f8 --- /dev/null +++ b/test/fixtures/all/vanilla/loaded.js @@ -0,0 +1,19 @@ +module.exports = function getString(i){ + if (typeof i === 'number'){ + if (isNaN(i)){ + return 'NaN' + } + else if (i === 0){ + return 'zero' + } + else if (i > 0){ + return 'positive' + } + else { + return 'negative' + } + } + else { + return 'wat?' + } +} \ No newline at end of file diff --git a/test/fixtures/all/vanilla/main.js b/test/fixtures/all/vanilla/main.js new file mode 100644 index 00000000..72b77148 --- /dev/null +++ b/test/fixtures/all/vanilla/main.js @@ -0,0 +1,4 @@ +const loaded = require('./loaded.js'); +console.log(loaded(0)) +console.log(loaded(1)) +console.log(loaded(-1)) \ No newline at end of file diff --git a/test/integration.js b/test/integration.js index 7caaf3c1..cffd1d2b 100644 --- a/test/integration.js +++ b/test/integration.js @@ -327,4 +327,47 @@ describe('c8', () => { output.toString('utf8').should.matchSnapshot() }) }) + describe('--all', () => { + it('reports coverage for unloaded js files as 0 for line, branch and function', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--temp-directory=tmp/vanilla-all', + '--clean=false', + '--all=true', + '--include=test/fixtures/all/vanilla/**/*.js', + '--exclude=**/*.ts', // add an exclude to avoid default excludes of test/** + nodePath, + require.resolve('./fixtures/all/vanilla/main') + ]) + output.toString('utf8').should.matchSnapshot() + }) + + it('reports coverage for unloaded transpiled ts files as 0 for line, branch and function', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--temp-directory=tmp/all-ts', + '--clean=false', + '--all=true', + '--include=test/fixtures/all/ts-compiled/**/*.ts', + '--exclude="test/*.js"', // add an exclude to avoid default excludes of test/** + nodePath, + require.resolve('./fixtures/all/ts-compiled/main') + ]) + output.toString('utf8').should.matchSnapshot() + }) + + it('reports coverage for unloaded ts files as 0 for line, branch and function when using ts-node', () => { + const { output } = spawnSync(nodePath, [ + c8Path, + '--temp-directory=tmp/all-ts-node', + '--clean=false', + '--all=true', + '--include=test/fixtures/all/ts-only/**/*.ts', + '--exclude="test/*.js"', // add an exclude to avoid default excludes of test/** + './node_modules/.bin/ts-node', + require.resolve('./fixtures/all/ts-only/main.ts') + ]) + output.toString('utf8').should.matchSnapshot() + }) + }) }) diff --git a/test/integration.js.snap b/test/integration.js.snap index b623cec5..e89955e2 100644 --- a/test/integration.js.snap +++ b/test/integration.js.snap @@ -14,6 +14,57 @@ All files | 86.21 | 91.67 | 66.67 | 86.21 | ," `; +exports[`c8 --all reports coverage for unloaded js files as 0 for line, branch and function 1`] = ` +",zero +positive +negative +--------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +--------------|----------|----------|----------|----------|-------------------| +All files | 64.29 | 66.67 | 50 | 64.29 | | + vanilla | 78.26 | 75 | 100 | 78.26 | | + loaded.js | 73.68 | 71.43 | 100 | 73.68 | 4,5,16,17,18 | + main.js | 100 | 100 | 100 | 100 | | + vanilla/dir | 0 | 0 | 0 | 0 | | + unloaded.js | 0 | 0 | 0 | 0 | 1,2,3,4,5 | +--------------|----------|----------|----------|----------|-------------------| +," +`; + +exports[`c8 --all reports coverage for unloaded transpiled ts files as 0 for line, branch and function 1`] = ` +",zero +positive +negative +-----------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +-----------------|----------|----------|----------|----------|-------------------| +All files | 0 | 0 | 0 | 0 | | + ts-compiled | 0 | 0 | 0 | 0 | | + loaded.ts | 0 | 0 | 0 | 0 |... 15,16,17,18,19 | + main.ts | 0 | 0 | 0 | 0 | 1,2,3,4 | + ts-compiled/dir | 0 | 0 | 0 | 0 | | + unloaded.ts | 0 | 0 | 0 | 0 | 1,2,3,4,5 | +-----------------|----------|----------|----------|----------|-------------------| +," +`; + +exports[`c8 --all reports coverage for unloaded ts files as 0 for line, branch and function when using ts-node 1`] = ` +",zero +positive +negative +--------------|----------|----------|----------|----------|-------------------| +File | % Stmts | % Branch | % Funcs | % Lines | Uncovered Line #s | +--------------|----------|----------|----------|----------|-------------------| +All files | 64.29 | 66.67 | 50 | 64.29 | | + ts-only | 78.26 | 75 | 100 | 78.26 | | + loaded.ts | 73.68 | 71.43 | 100 | 73.68 | 4,5,16,17,18 | + main.ts | 100 | 100 | 100 | 100 | | + ts-only/dir | 0 | 0 | 0 | 0 | | + unloaded.ts | 0 | 0 | 0 | 0 | 1,2,3,4,5 | +--------------|----------|----------|----------|----------|-------------------| +," +`; + exports[`c8 ESM Modules collects coverage for ESM modules 1`] = ` ",bar foo ------------|----------|----------|----------|----------|-------------------|