Skip to content

Commit 10b8971

Browse files
authoredApr 5, 2024··
feat(coverage): v8 to ignore empty lines, comments, types (#5457)
1 parent d400388 commit 10b8971

17 files changed

+1227
-156
lines changed
 

‎docs/config/index.md

+46
Original file line numberDiff line numberDiff line change
@@ -1103,6 +1103,20 @@ List of files included in coverage as glob patterns
11031103

11041104
List of files excluded from coverage as glob patterns.
11051105

1106+
This option overrides all default options. Extend the default options when adding new patterns to ignore:
1107+
1108+
```ts
1109+
import { coverageConfigDefaults, defineConfig } from 'vitest/config'
1110+
1111+
export default defineConfig({
1112+
test: {
1113+
coverage: {
1114+
exclude: ['**/custom-pattern/**', ...coverageConfigDefaults.exclude]
1115+
},
1116+
},
1117+
})
1118+
```
1119+
11061120
#### coverage.all
11071121

11081122
- **Type:** `boolean`
@@ -1320,6 +1334,38 @@ Sets thresholds for files matching the glob pattern.
13201334
}
13211335
```
13221336

1337+
#### coverage.ignoreEmptyLines
1338+
1339+
- **Type:** `boolean`
1340+
- **Default:** `false`
1341+
- **Available for providers:** `'v8'`
1342+
- **CLI:** `--coverage.ignoreEmptyLines=<boolean>`
1343+
1344+
Ignore empty lines, comments and other non-runtime code, e.g. Typescript types.
1345+
1346+
This option works only if the used compiler removes comments and other non-runtime code from the transpiled code.
1347+
By default Vite uses ESBuild which removes comments and Typescript types from `.ts`, `.tsx` and `.jsx` files.
1348+
1349+
If you want to apply ESBuild to other files as well, define them in [`esbuild` options](https://vitejs.dev/config/shared-options.html#esbuild):
1350+
1351+
```ts
1352+
import { defineConfig } from 'vitest/config'
1353+
1354+
export default defineConfig({
1355+
esbuild: {
1356+
// Transpile all files with ESBuild to remove comments from code coverage.
1357+
// Required for `test.coverage.ignoreEmptyLines` to work:
1358+
include: ['**/*.js', '**/*.jsx', '**/*.mjs', '**/*.ts', '**/*.tsx'],
1359+
},
1360+
test: {
1361+
coverage: {
1362+
provider: 'v8',
1363+
ignoreEmptyLines: true,
1364+
},
1365+
},
1366+
})
1367+
```
1368+
13231369
#### coverage.ignoreClassMethods
13241370

13251371
- **Type:** `string[]`

‎docs/guide/coverage.md

+5
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,11 @@ npm i -D @vitest/coverage-istanbul
4343

4444
## Coverage Setup
4545

46+
:::tip
47+
It's recommended to always define [`coverage.include`](https://vitest.dev/config/#coverage-include) in your configuration file.
48+
This helps Vitest to reduce the amount of files picked by [`coverage.all`](https://vitest.dev/config/#coverage-all).
49+
:::
50+
4651
To test with coverage enabled, you can pass the `--coverage` flag in CLI.
4752
By default, reporter `['text', 'html', 'clover', 'json']` will be used.
4853

‎package.json

+2-1
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,8 @@
8484
"@types/chai@4.3.6": "patches/@types__chai@4.3.6.patch",
8585
"@sinonjs/fake-timers@11.1.0": "patches/@sinonjs__fake-timers@11.1.0.patch",
8686
"cac@6.7.14": "patches/cac@6.7.14.patch",
87-
"@types/sinonjs__fake-timers@8.1.5": "patches/@types__sinonjs__fake-timers@8.1.5.patch"
87+
"@types/sinonjs__fake-timers@8.1.5": "patches/@types__sinonjs__fake-timers@8.1.5.patch",
88+
"v8-to-istanbul@9.2.0": "patches/v8-to-istanbul@9.2.0.patch"
8889
}
8990
},
9091
"simple-git-hooks": {

‎packages/coverage-v8/package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -56,8 +56,7 @@
5656
"picocolors": "^1.0.0",
5757
"std-env": "^3.5.0",
5858
"strip-literal": "^2.0.0",
59-
"test-exclude": "^6.0.0",
60-
"v8-to-istanbul": "^9.2.0"
59+
"test-exclude": "^6.0.0"
6160
},
6261
"devDependencies": {
6362
"@types/debug": "^4.1.12",
@@ -66,6 +65,7 @@
6665
"@types/istanbul-lib-source-maps": "^4.0.4",
6766
"@types/istanbul-reports": "^3.0.4",
6867
"pathe": "^1.1.1",
68+
"v8-to-istanbul": "^9.2.0",
6969
"vite-node": "workspace:*",
7070
"vitest": "workspace:*"
7171
}

‎packages/coverage-v8/src/provider.ts

+5-7
Original file line numberDiff line numberDiff line change
@@ -266,14 +266,12 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
266266
}
267267

268268
const coverages = await Promise.all(chunk.map(async (filename) => {
269-
const transformResult = await this.ctx.vitenode.transformRequest(filename.pathname).catch(() => {})
269+
const { originalSource, source } = await this.getSources(filename.href, transformResults)
270270

271271
// Ignore empty files, e.g. files that contain only typescript types and no runtime code
272-
if (transformResult && stripLiteral(transformResult.code).trim() === '')
272+
if (source && stripLiteral(source).trim() === '')
273273
return null
274274

275-
const { originalSource } = await this.getSources(filename.href, transformResults)
276-
277275
const coverage = {
278276
url: filename.href,
279277
scriptId: '0',
@@ -309,9 +307,9 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
309307
}> {
310308
const filePath = normalize(fileURLToPath(url))
311309

312-
const transformResult = transformResults.get(filePath)
310+
const transformResult = transformResults.get(filePath) || await this.ctx.vitenode.transformRequest(filePath).catch(() => {})
313311

314-
const map = transformResult?.map
312+
const map = transformResult?.map as (EncodedSourceMap | undefined)
315313
const code = transformResult?.code
316314
const sourcesContent = map?.sourcesContent?.[0] || await fs.readFile(filePath, 'utf-8').catch(() => {
317315
// If file does not exist construct a dummy source for it.
@@ -367,7 +365,7 @@ export class V8CoverageProvider extends BaseCoverageProvider implements Coverage
367365
// If no source map was found from vite-node we can assume this file was not run in the wrapper
368366
const wrapperLength = sources.sourceMap ? WRAPPER_LENGTH : 0
369367

370-
const converter = v8ToIstanbul(url, wrapperLength, sources)
368+
const converter = v8ToIstanbul(url, wrapperLength, sources, undefined, this.options.ignoreEmptyLines)
371369
await converter.load()
372370

373371
converter.applyCoverage(functions)

‎packages/vitest/src/defaults.ts

+1
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ export const coverageConfigDefaults: ResolvedCoverageOptions = {
4343
reporter: [['text', {}], ['html', {}], ['clover', {}], ['json', {}]],
4444
extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte', '.marko'],
4545
allowExternal: false,
46+
ignoreEmptyLines: false,
4647
processingConcurrency: Math.min(20, os.availableParallelism?.() ?? os.cpus().length),
4748
}
4849

‎packages/vitest/src/types/coverage.ts

+6-1
Original file line numberDiff line numberDiff line change
@@ -233,7 +233,12 @@ export interface CoverageIstanbulOptions extends BaseCoverageOptions {
233233
ignoreClassMethods?: string[]
234234
}
235235

236-
export interface CoverageV8Options extends BaseCoverageOptions {}
236+
export interface CoverageV8Options extends BaseCoverageOptions {
237+
/**
238+
* Ignore empty lines, comments and other non-runtime code, e.g. Typescript types
239+
*/
240+
ignoreEmptyLines?: boolean
241+
}
237242

238243
export interface CustomProviderOptions extends Pick<BaseCoverageOptions, FieldsWithDefaultValues> {
239244
/** Name of the module or path to a file to load the custom provider from */

‎patches/v8-to-istanbul@9.2.0.patch

+156
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
diff --git a/CHANGELOG.md b/CHANGELOG.md
2+
deleted file mode 100644
3+
index 4f7e3bc8d1bba4feb51044ff9eb77b41f972f957..0000000000000000000000000000000000000000
4+
diff --git a/index.d.ts b/index.d.ts
5+
index ee7b286844f2bf96357218166e26e1c338f774cf..657531b7c75f43e9a4e957dd1f10797e44da5bb1 100644
6+
--- a/index.d.ts
7+
+++ b/index.d.ts
8+
@@ -1,5 +1,7 @@
9+
/// <reference types="node" />
10+
11+
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
12+
+
13+
import { Profiler } from 'inspector'
14+
import { CoverageMapData } from 'istanbul-lib-coverage'
15+
import { SourceMapInput } from '@jridgewell/trace-mapping'
16+
@@ -20,6 +22,6 @@ declare class V8ToIstanbul {
17+
toIstanbul(): CoverageMapData
18+
}
19+
20+
-declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean): V8ToIstanbul
21+
+declare function v8ToIstanbul(scriptPath: string, wrapperLength?: number, sources?: Sources, excludePath?: (path: string) => boolean, excludeEmptyLines?: boolean): V8ToIstanbul
22+
23+
export = v8ToIstanbul
24+
diff --git a/index.js b/index.js
25+
index 4db27a7d84324d0e6605c5506e3eee5665ddfeb0..7bfb839634b1e3c54efedc3c270d82edc4167a64 100644
26+
--- a/index.js
27+
+++ b/index.js
28+
@@ -1,5 +1,6 @@
29+
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
30+
const V8ToIstanbul = require('./lib/v8-to-istanbul')
31+
32+
-module.exports = function (path, wrapperLength, sources, excludePath) {
33+
- return new V8ToIstanbul(path, wrapperLength, sources, excludePath)
34+
+module.exports = function (path, wrapperLength, sources, excludePath, excludeEmptyLines) {
35+
+ return new V8ToIstanbul(path, wrapperLength, sources, excludePath, excludeEmptyLines)
36+
}
37+
diff --git a/lib/source.js b/lib/source.js
38+
index d8ebc215f6ad83d472abafe976935acfe5c61b04..021fd2aed1f73ebb4adc449ce6e96f2d89c295a5 100644
39+
--- a/lib/source.js
40+
+++ b/lib/source.js
41+
@@ -1,23 +1,32 @@
42+
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
43+
const CovLine = require('./line')
44+
const { sliceRange } = require('./range')
45+
-const { originalPositionFor, generatedPositionFor, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')
46+
+const { originalPositionFor, generatedPositionFor, eachMapping, GREATEST_LOWER_BOUND, LEAST_UPPER_BOUND } = require('@jridgewell/trace-mapping')
47+
48+
module.exports = class CovSource {
49+
- constructor (sourceRaw, wrapperLength) {
50+
+ constructor (sourceRaw, wrapperLength, traceMap) {
51+
sourceRaw = sourceRaw ? sourceRaw.trimEnd() : ''
52+
this.lines = []
53+
this.eof = sourceRaw.length
54+
this.shebangLength = getShebangLength(sourceRaw)
55+
this.wrapperLength = wrapperLength - this.shebangLength
56+
- this._buildLines(sourceRaw)
57+
+ this._buildLines(sourceRaw, traceMap)
58+
}
59+
60+
- _buildLines (source) {
61+
+ _buildLines (source, traceMap) {
62+
let position = 0
63+
let ignoreCount = 0
64+
let ignoreAll = false
65+
+ const linesToCover = traceMap && this._parseLinesToCover(traceMap)
66+
+
67+
for (const [i, lineStr] of source.split(/(?<=\r?\n)/u).entries()) {
68+
- const line = new CovLine(i + 1, position, lineStr)
69+
+ const lineNumber = i + 1
70+
+ const line = new CovLine(lineNumber, position, lineStr)
71+
+
72+
+ if (linesToCover && !linesToCover.has(lineNumber)) {
73+
+ line.ignore = true
74+
+ }
75+
+
76+
if (ignoreCount > 0) {
77+
line.ignore = true
78+
ignoreCount--
79+
@@ -125,6 +134,18 @@ module.exports = class CovSource {
80+
if (this.lines[line - 1] === undefined) return this.eof
81+
return Math.min(this.lines[line - 1].startCol + relCol, this.lines[line - 1].endCol)
82+
}
83+
+
84+
+ _parseLinesToCover (traceMap) {
85+
+ const linesToCover = new Set()
86+
+
87+
+ eachMapping(traceMap, (mapping) => {
88+
+ if (mapping.originalLine !== null) {
89+
+ linesToCover.add(mapping.originalLine)
90+
+ }
91+
+ })
92+
+
93+
+ return linesToCover
94+
+ }
95+
}
96+
97+
// this implementation is pulled over from istanbul-lib-sourcemap:
98+
diff --git a/lib/v8-to-istanbul.js b/lib/v8-to-istanbul.js
99+
index 3616437b00658861dc5a8910c64d1449e9fdf467..c1e0c0ae19984480e408713d1691fa174a7c4c1f 100644
100+
--- a/lib/v8-to-istanbul.js
101+
+++ b/lib/v8-to-istanbul.js
102+
@@ -1,3 +1,4 @@
103+
+// Patch applied: https://github.com/istanbuljs/v8-to-istanbul/pull/244
104+
const assert = require('assert')
105+
const convertSourceMap = require('convert-source-map')
106+
const util = require('util')
107+
@@ -25,12 +26,13 @@ const isNode8 = /^v8\./.test(process.version)
108+
const cjsWrapperLength = isOlderNode10 ? require('module').wrapper[0].length : 0
109+
110+
module.exports = class V8ToIstanbul {
111+
- constructor (scriptPath, wrapperLength, sources, excludePath) {
112+
+ constructor (scriptPath, wrapperLength, sources, excludePath, excludeEmptyLines) {
113+
assert(typeof scriptPath === 'string', 'scriptPath must be a string')
114+
assert(!isNode8, 'This module does not support node 8 or lower, please upgrade to node 10')
115+
this.path = parsePath(scriptPath)
116+
this.wrapperLength = wrapperLength === undefined ? cjsWrapperLength : wrapperLength
117+
this.excludePath = excludePath || (() => false)
118+
+ this.excludeEmptyLines = excludeEmptyLines === true
119+
this.sources = sources || {}
120+
this.generatedLines = []
121+
this.branches = {}
122+
@@ -58,8 +60,8 @@ module.exports = class V8ToIstanbul {
123+
if (!this.sourceMap.sourcesContent) {
124+
this.sourceMap.sourcesContent = await this.sourcesContentFromSources()
125+
}
126+
- this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength), path: this.sourceMap.sources[i] }))
127+
- this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
128+
+ this.covSources = this.sourceMap.sourcesContent.map((rawSource, i) => ({ source: new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.sourceMap.sources[i] }))
129+
+ this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
130+
} else {
131+
const candidatePath = this.rawSourceMap.sourcemap.sources.length >= 1 ? this.rawSourceMap.sourcemap.sources[0] : this.rawSourceMap.sourcemap.file
132+
this.path = this._resolveSource(this.rawSourceMap, candidatePath || this.path)
133+
@@ -82,8 +84,8 @@ module.exports = class V8ToIstanbul {
134+
// We fallback to reading the original source from disk.
135+
originalRawSource = await readFile(this.path, 'utf8')
136+
}
137+
- this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength), path: this.path }]
138+
- this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength)
139+
+ this.covSources = [{ source: new CovSource(originalRawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null), path: this.path }]
140+
+ this.sourceTranspiled = new CovSource(rawSource, this.wrapperLength, this.excludeEmptyLines ? this.sourceMap : null)
141+
}
142+
} else {
143+
this.covSources = [{ source: new CovSource(rawSource, this.wrapperLength), path: this.path }]
144+
@@ -281,8 +283,10 @@ module.exports = class V8ToIstanbul {
145+
s: {}
146+
}
147+
source.lines.forEach((line, index) => {
148+
- statements.statementMap[`${index}`] = line.toIstanbul()
149+
- statements.s[`${index}`] = line.ignore ? 1 : line.count
150+
+ if (!line.ignore) {
151+
+ statements.statementMap[`${index}`] = line.toIstanbul()
152+
+ statements.s[`${index}`] = line.count
153+
+ }
154+
})
155+
return statements
156+
}

‎pnpm-lock.yaml

+12-5
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

‎test/coverage-test/coverage-report-tests/__snapshots__/istanbul.report.test.ts.snap

+294-44
Large diffs are not rendered by default.

‎test/coverage-test/coverage-report-tests/__snapshots__/v8.report.test.ts.snap

+517-95
Large diffs are not rendered by default.

‎test/coverage-test/coverage-report-tests/changed.test.ts

+2
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ test('report contains only the changed files', async () => {
77
const coverageJson = await readCoverageJson('./coverage/coverage-final.json')
88
const coverageMap = libCoverage.createCoverageMap(coverageJson as any)
99

10+
// Note that this test may fail if you have new files in "vitest/test/coverage-test/src"
11+
// and have not yet committed those
1012
expect(coverageMap.files()).toMatchInlineSnapshot(`
1113
[
1214
"<process-cwd>/src/file-to-change.ts",
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
import { beforeAll, expect, test } from 'vitest'
2+
import libCoverage from 'istanbul-lib-coverage'
3+
4+
import { readCoverageJson } from './utils'
5+
6+
type CoveredLine = 1
7+
type UncoveredLine = 0
8+
type IgnoredLine = undefined
9+
10+
// Key is 1-based line number
11+
type LineCoverage = Record<number, CoveredLine | UncoveredLine | IgnoredLine>
12+
13+
let coveredFileLines: LineCoverage
14+
let uncoveredFileLines: LineCoverage
15+
16+
beforeAll(async () => {
17+
const coverageJson = await readCoverageJson('./coverage/coverage-final.json')
18+
const coverageMap = libCoverage.createCoverageMap(coverageJson as any)
19+
20+
coveredFileLines = coverageMap.fileCoverageFor('<process-cwd>/src/empty-lines.ts').getLineCoverage() as typeof coveredFileLines
21+
uncoveredFileLines = coverageMap.fileCoverageFor('<process-cwd>/src/untested-file.ts').getLineCoverage() as typeof uncoveredFileLines
22+
})
23+
24+
test('empty lines are ignored', async () => {
25+
expect(coveredFileLines[12]).toBe(undefined)
26+
expect(coveredFileLines[14]).toBe(undefined)
27+
expect(coveredFileLines[19]).toBe(undefined)
28+
expect(coveredFileLines[27]).toBe(undefined)
29+
expect(coveredFileLines[30]).toBe(undefined)
30+
31+
expect(uncoveredFileLines[5]).toBe(undefined)
32+
expect(uncoveredFileLines[7]).toBe(undefined)
33+
})
34+
35+
test('comments are ignored', async () => {
36+
expect(coveredFileLines[1]).toBe(undefined)
37+
expect(coveredFileLines[3]).toBe(undefined)
38+
expect(coveredFileLines[4]).toBe(undefined)
39+
expect(coveredFileLines[5]).toBe(undefined)
40+
expect(coveredFileLines[6]).toBe(undefined)
41+
expect(coveredFileLines[7]).toBe(undefined)
42+
expect(coveredFileLines[9]).toBe(undefined)
43+
expect(coveredFileLines[16]).toBe(undefined)
44+
45+
expect(uncoveredFileLines[1]).toBe(undefined)
46+
expect(uncoveredFileLines[2]).toBe(undefined)
47+
expect(uncoveredFileLines[3]).toBe(undefined)
48+
expect(uncoveredFileLines[4]).toBe(undefined)
49+
expect(uncoveredFileLines[6]).toBe(undefined)
50+
expect(uncoveredFileLines[13]).toBe(undefined)
51+
expect(uncoveredFileLines[20]).toBe(undefined)
52+
expect(uncoveredFileLines[34]).toBe(undefined)
53+
expect(uncoveredFileLines[45]).toBe(undefined)
54+
})
55+
56+
test('ignore hints are ignored', () => {
57+
expect(uncoveredFileLines[38]).toBe(undefined)
58+
expect(uncoveredFileLines[39]).toBe(undefined)
59+
expect(uncoveredFileLines[40]).toBe(undefined)
60+
expect(uncoveredFileLines[41]).toBe(undefined)
61+
expect(uncoveredFileLines[42]).toBe(undefined)
62+
expect(uncoveredFileLines[43]).toBe(undefined)
63+
})
64+
65+
test('typescript types are ignored', () => {
66+
expect(coveredFileLines[13]).toBe(undefined)
67+
expect(coveredFileLines[20]).toBe(undefined)
68+
expect(coveredFileLines[21]).toBe(undefined)
69+
expect(coveredFileLines[22]).toBe(undefined)
70+
expect(coveredFileLines[23]).toBe(undefined)
71+
expect(coveredFileLines[24]).toBe(undefined)
72+
expect(coveredFileLines[25]).toBe(undefined)
73+
expect(coveredFileLines[26]).toBe(undefined)
74+
75+
expect(uncoveredFileLines[17]).toBe(undefined)
76+
expect(uncoveredFileLines[25]).toBe(undefined)
77+
expect(uncoveredFileLines[26]).toBe(undefined)
78+
expect(uncoveredFileLines[27]).toBe(undefined)
79+
expect(uncoveredFileLines[28]).toBe(undefined)
80+
expect(uncoveredFileLines[29]).toBe(undefined)
81+
expect(uncoveredFileLines[30]).toBe(undefined)
82+
expect(uncoveredFileLines[31]).toBe(undefined)
83+
})
84+
85+
test('runtime code is not ignored', () => {
86+
// Covered
87+
expect(coveredFileLines[2]).toBe(1)
88+
expect(coveredFileLines[8]).toBe(1)
89+
expect(coveredFileLines[15]).toBe(1)
90+
expect(coveredFileLines[28]).toBe(1)
91+
92+
// Uncovered
93+
expect(coveredFileLines[10]).toBe(0)
94+
expect(coveredFileLines[17]).toBe(0)
95+
96+
// Uncovered
97+
expect(uncoveredFileLines[8]).toBe(0)
98+
expect(uncoveredFileLines[9]).toBe(0)
99+
expect(uncoveredFileLines[10]).toBe(0)
100+
expect(uncoveredFileLines[12]).toBe(0)
101+
expect(uncoveredFileLines[14]).toBe(0)
102+
expect(uncoveredFileLines[19]).toBe(0)
103+
expect(uncoveredFileLines[21]).toBe(0)
104+
expect(uncoveredFileLines[24]).toBe(0)
105+
expect(uncoveredFileLines[33]).toBe(0)
106+
expect(uncoveredFileLines[35]).toBe(0)
107+
expect(uncoveredFileLines[46]).toBe(0)
108+
})
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
import { expect, test } from 'vitest'
2+
3+
import { add } from '../src/empty-lines'
4+
5+
test('cover some lines', () => {
6+
expect(add(10, 20)).toBe(30)
7+
})

‎test/coverage-test/src/empty-lines.ts

+29
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
/* eslint-disable unused-imports/no-unused-vars -- intentional */
2+
export function add(a: number, b: number) {
3+
/**
4+
* Multi
5+
* line
6+
* comment
7+
*/
8+
if (a === 2 && b === 3) {
9+
// This line should NOT be covered
10+
return 5
11+
}
12+
13+
type TypescriptTypings = 1 | 2
14+
15+
if (a === 1 && b === 1) {
16+
// This line should NOT be covered
17+
return 2
18+
}
19+
20+
interface MoreCompileTimeCode {
21+
should: {
22+
be: {
23+
excluded: true
24+
}
25+
}
26+
}
27+
28+
return a + b
29+
}

‎test/coverage-test/src/untested-file.ts

+15-1
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@
33
* if sourcemaps are off.
44
*/
55

6+
/* eslint-disable unused-imports/no-unused-vars -- intentional */
7+
68
export default function untestedFile() {
79
return 'This file should end up in report when {"all": true} is given'
810
}
@@ -12,21 +14,33 @@ function add(a: number, b: number) {
1214
return a + b
1315
}
1416

17+
type TypescriptTypings = 1 | 2
18+
1519
function multiply(a: number, b: number) {
1620
// This line should NOT be covered
1721
return a * b
1822
}
1923

2024
export function math(a: number, b: number, operator: '*' | '+') {
25+
interface MoreCompileTimeCode {
26+
should: {
27+
be: {
28+
excluded: true
29+
}
30+
}
31+
}
32+
2133
if (operator === '*') {
2234
// This line should NOT be covered
2335
return multiply(a, b)
2436
}
2537

38+
/* v8 ignore start */
2639
if (operator === '+') {
27-
// This line should NOT be covered
40+
// This line should be excluded
2841
return add(a, b)
2942
}
43+
/* v8 ignore stop */
3044

3145
// This line should NOT be covered
3246
throw new Error('Unsupported operator')

‎test/coverage-test/testing-options.mjs

+20
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,30 @@ const testCases = [
9090
rmSync('./src/new-uncovered-file.ts')
9191
},
9292
},
93+
{
94+
testConfig: {
95+
name: 'ignore empty lines',
96+
include: ['option-tests/empty-lines.test.ts'],
97+
coverage: {
98+
provider: 'v8',
99+
reporter: 'json',
100+
ignoreEmptyLines: true,
101+
all: true,
102+
include: ['src/empty-lines.ts', 'src/untested-file.ts'],
103+
},
104+
},
105+
assertionConfig: {
106+
include: ['coverage-report-tests/empty-lines.test.ts'],
107+
},
108+
},
93109
]
94110

95111
for (const provider of ['v8', 'istanbul']) {
96112
for (const { after, before, testConfig, assertionConfig } of testCases) {
113+
// Test config may specify which provider the test is for
114+
if (testConfig.coverage?.provider && testConfig.coverage.provider !== provider)
115+
continue
116+
97117
await before?.()
98118

99119
// Run test case

0 commit comments

Comments
 (0)
Please sign in to comment.