Skip to content

Commit 2eb631e

Browse files
j03mBenjamin E. Coe
authored andcommitted
feat: adds --all functionality (#158)
1 parent 97b9769 commit 2eb631e

25 files changed

+313
-10
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,3 +3,4 @@ node_modules
33
.nyc_output
44
coverage
55
tmp
6+
.idea

README.md

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,16 @@ c8 node foo.js
1616

1717
The above example will output coverage metrics for `foo.js`.
1818

19+
## Checking for "full" source coverage using `--all`
20+
21+
By default v8 will only give us coverage for files that were loaded by the engine. If there are source files in your
22+
project that are flexed in production but not in your tests, your coverage numbers will not reflect this. For example,
23+
if your project's `main.js` loads `a.js` and `b.js` but your unit tests only load `a.js` your total coverage
24+
could show as `100%` for `a.js` when in fact both `main.js` and `b.js` are uncovered.
25+
26+
By supplying `--all` to c8, all files in `cwd` that pass the `--include` and `--exclude` flag checks, will be loaded into the
27+
report. If any of those files remain uncovered they will be factored into the report with a default of 0% coverage.
28+
1929
## c8 report
2030

2131
run `c8 report` to regenerate reports after `c8` has already been run.

lib/commands/report.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,8 @@ exports.outputReport = async function (argv) {
1919
watermarks: argv.watermarks,
2020
resolve: argv.resolve,
2121
omitRelative: argv.omitRelative,
22-
wrapperLength: argv.wrapperLength
22+
wrapperLength: argv.wrapperLength,
23+
all: argv.all
2324
})
2425
await report.run()
2526
if (argv.checkCoverage) checkCoverages(argv, report)

lib/parse-args.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,12 @@ function buildYargs (withCommands = false) {
8383
type: 'boolean',
8484
describe: 'should temp files be deleted before script execution'
8585
})
86+
.options('all', {
87+
default: false,
88+
type: 'boolean',
89+
describe: 'supplying --all will cause c8 to consider all src files in the current working directory ' +
90+
'when the determining coverage. Respects include/exclude.'
91+
})
8692
.pkgConf('c8')
8793
.config(config)
8894
.demandCommand(1)

lib/report.js

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,9 @@ const furi = require('furi')
33
const libCoverage = require('istanbul-lib-coverage')
44
const libReport = require('istanbul-lib-report')
55
const reports = require('istanbul-reports')
6-
const { readdirSync, readFileSync } = require('fs')
7-
const { isAbsolute, resolve } = require('path')
6+
const { readdirSync, readFileSync, statSync } = require('fs')
7+
const { isAbsolute, resolve, extname } = require('path')
8+
const getSourceMapFromFile = require('./source-map-from-file')
89
// TODO: switch back to @c88/v8-coverage once patch is landed.
910
const v8toIstanbul = require('v8-to-istanbul')
1011
const isCjsEsmBridgeCov = require('./is-cjs-esm-bridge')
@@ -19,7 +20,8 @@ class Report {
1920
watermarks,
2021
omitRelative,
2122
wrapperLength,
22-
resolve: resolvePaths
23+
resolve: resolvePaths,
24+
all
2325
}) {
2426
this.reporter = reporter
2527
this.reportsDirectory = reportsDirectory
@@ -33,6 +35,8 @@ class Report {
3335
this.omitRelative = omitRelative
3436
this.sourceMapCache = {}
3537
this.wrapperLength = wrapperLength
38+
this.all = all
39+
this.src = process.cwd()
3640
}
3741

3842
async run () {
@@ -56,8 +60,8 @@ class Report {
5660
// use-case.
5761
if (this._allCoverageFiles) return this._allCoverageFiles
5862

63+
const map = libCoverage.createCoverageMap()
5964
const v8ProcessCov = this._getMergedProcessCov()
60-
const map = libCoverage.createCoverageMap({})
6165
const resultCountPerPath = new Map()
6266
const possibleCjsEsmBridges = new Map()
6367

@@ -94,7 +98,6 @@ class Report {
9498
map.merge(converter.toIstanbul())
9599
}
96100
}
97-
98101
this._allCoverageFiles = map
99102
return this._allCoverageFiles
100103
}
@@ -139,14 +142,50 @@ class Report {
139142
_getMergedProcessCov () {
140143
const { mergeProcessCovs } = require('@bcoe/v8-coverage')
141144
const v8ProcessCovs = []
145+
const fileIndex = new Set() // Set<string>
142146
for (const v8ProcessCov of this._loadReports()) {
143147
if (this._isCoverageObject(v8ProcessCov)) {
144148
if (v8ProcessCov['source-map-cache']) {
145149
Object.assign(this.sourceMapCache, v8ProcessCov['source-map-cache'])
146150
}
147-
v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov))
151+
v8ProcessCovs.push(this._normalizeProcessCov(v8ProcessCov, fileIndex))
148152
}
149153
}
154+
155+
if (this.all) {
156+
const emptyReports = []
157+
v8ProcessCovs.unshift({
158+
result: emptyReports
159+
})
160+
const workingDir = process.cwd()
161+
this.exclude.globSync(workingDir).forEach((f) => {
162+
const fullPath = resolve(workingDir, f)
163+
if (!fileIndex.has(fullPath)) {
164+
const ext = extname(f)
165+
if (ext === '.js' || ext === '.ts' || ext === '.mjs') {
166+
const stat = statSync(f)
167+
const sourceMap = getSourceMapFromFile(f)
168+
if (sourceMap !== undefined) {
169+
this.sourceMapCache[`file://${fullPath}`] = { data: JSON.parse(readFileSync(sourceMap).toString()) }
170+
}
171+
emptyReports.push({
172+
scriptId: 0,
173+
url: resolve(f),
174+
functions: [{
175+
functionName: '(empty-report)',
176+
ranges: [{
177+
startOffset: 0,
178+
endOffset: stat.size,
179+
count: 0
180+
}],
181+
isBlockCoverage: true
182+
}]
183+
})
184+
}
185+
}
186+
})
187+
}
188+
150189
return mergeProcessCovs(v8ProcessCovs)
151190
}
152191

@@ -193,15 +232,17 @@ class Report {
193232
* There is no deep cloning.
194233
*
195234
* @param v8ProcessCov V8 process coverage to normalize.
235+
* @param fileIndex a Set<string> of paths discovered in coverage
196236
* @return {v8ProcessCov} Normalized V8 process coverage.
197237
* @private
198238
*/
199-
_normalizeProcessCov (v8ProcessCov) {
239+
_normalizeProcessCov (v8ProcessCov, fileIndex) {
200240
const result = []
201241
for (const v8ScriptCov of v8ProcessCov.result) {
202242
if (/^file:\/\//.test(v8ScriptCov.url)) {
203243
try {
204244
v8ScriptCov.url = furi.toSysPath(v8ScriptCov.url)
245+
fileIndex.add(v8ScriptCov.url)
205246
} catch (err) {
206247
console.warn(err)
207248
continue

lib/source-map-from-file.js

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
const { isAbsolute, join, dirname } = require('path')
2+
const { readFileSync } = require('fs')
3+
/**
4+
* Extract the sourcemap url from a source file
5+
* reference: https://sourcemaps.info/spec.html
6+
* @param {String} file - compilation target file
7+
* @returns {String} full path to source map file
8+
* @private
9+
*/
10+
function getSourceMapFromFile (file) {
11+
const fileBody = readFileSync(file).toString()
12+
const sourceMapLineRE = /\/\/[#@] ?sourceMappingURL=([^\s'"]+)\s*$/mg
13+
const results = fileBody.match(sourceMapLineRE)
14+
if (results !== null) {
15+
const sourceMap = results[results.length - 1].split('=')[1]
16+
if (isAbsolute(sourceMap)) {
17+
return sourceMap
18+
} else {
19+
const base = dirname(file)
20+
return join(base, sourceMap)
21+
}
22+
}
23+
}
24+
25+
module.exports = getSourceMapFromFile

package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -61,4 +61,4 @@
6161
"bin",
6262
"LICENSE"
6363
]
64-
}
64+
}

test/fixtures/all/ts-compiled/dir/unloaded.js

Lines changed: 8 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/all/ts-compiled/dir/unloaded.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function Unloaded(){
2+
return 'Never loaded :('
3+
}
4+
5+
console.log("This file shouldn't have been evaluated")

test/fixtures/all/ts-compiled/loaded.js

Lines changed: 23 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/all/ts-compiled/loaded.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default function getString(i){
2+
if (typeof i === 'number'){
3+
if (isNaN(i)){
4+
return 'NaN'
5+
}
6+
else if (i === 0){
7+
return 'zero'
8+
}
9+
else if (i > 0){
10+
return 'positive'
11+
}
12+
else {
13+
return 'negative'
14+
}
15+
}
16+
else {
17+
return 'wat?'
18+
}
19+
}

test/fixtures/all/ts-compiled/main.js

Lines changed: 7 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/all/ts-compiled/main.js.map

Lines changed: 1 addition & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

test/fixtures/all/ts-compiled/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import getString from "./loaded"
2+
console.log(getString(0))
3+
console.log(getString(1))
4+
console.log(getString(-1))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
export default function Unloaded(){
2+
return 'Never loaded :('
3+
}
4+
5+
console.log("This file shouldn't have been evaluated")

test/fixtures/all/ts-only/loaded.ts

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
export default function getString(i){
2+
if (typeof i === 'number'){
3+
if (isNaN(i)){
4+
return 'NaN'
5+
}
6+
else if (i === 0){
7+
return 'zero'
8+
}
9+
else if (i > 0){
10+
return 'positive'
11+
}
12+
else {
13+
return 'negative'
14+
}
15+
}
16+
else {
17+
return 'wat?'
18+
}
19+
}

test/fixtures/all/ts-only/main.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
import getString from "./loaded"
2+
console.log(getString(0))
3+
console.log(getString(1))
4+
console.log(getString(-1))
Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
module.exports = function Unloaded(){
2+
return 'Never loaded :('
3+
}
4+
5+
console.log("This file shouldn't have been evaluated")

test/fixtures/all/vanilla/loaded.js

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
module.exports = function getString(i){
2+
if (typeof i === 'number'){
3+
if (isNaN(i)){
4+
return 'NaN'
5+
}
6+
else if (i === 0){
7+
return 'zero'
8+
}
9+
else if (i > 0){
10+
return 'positive'
11+
}
12+
else {
13+
return 'negative'
14+
}
15+
}
16+
else {
17+
return 'wat?'
18+
}
19+
}

test/fixtures/all/vanilla/main.js

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
const loaded = require('./loaded.js');
2+
console.log(loaded(0))
3+
console.log(loaded(1))
4+
console.log(loaded(-1))

0 commit comments

Comments
 (0)