From a6ecc930f2c1602cc4d83926af8c71f242f9448a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ari=20Perkki=C3=B6?= Date: Thu, 18 Aug 2022 18:38:48 +0000 Subject: [PATCH] feat(coverage-istanbul): add "all" option - Similar as nyc and c8 has --- docs/config/index.md | 7 +++ packages/coverage-istanbul/package.json | 1 + packages/coverage-istanbul/src/provider.ts | 47 ++++++++++++++++++- packages/vitest/src/types/coverage.ts | 6 ++- pnpm-lock.yaml | 2 + .../coverage-test/coverage.istanbul.test.ts | 9 +++- test/coverage-test/src/untested-file.ts | 3 ++ test/coverage-test/vitest.config.ts | 1 + 8 files changed, 72 insertions(+), 4 deletions(-) create mode 100644 test/coverage-test/src/untested-file.ts diff --git a/docs/config/index.md b/docs/config/index.md index a868895c87a7..498a1947ff77 100644 --- a/docs/config/index.md +++ b/docs/config/index.md @@ -532,6 +532,13 @@ Set to array of class method names to ignore for coverage. Watermarks for statements, lines, branches and functions. +##### all + +- **Type:** `boolean` +- **Default:** false + +Whether to include all files, including the untested ones into report. + ### testNamePattern - **Type** `string | RegExp` diff --git a/packages/coverage-istanbul/package.json b/packages/coverage-istanbul/package.json index cb9df3b2614c..545e83f2c842 100644 --- a/packages/coverage-istanbul/package.json +++ b/packages/coverage-istanbul/package.json @@ -55,6 +55,7 @@ "@types/istanbul-lib-report": "^3.0.0", "@types/istanbul-lib-source-maps": "^4.0.1", "@types/istanbul-reports": "^3.0.1", + "p-limit": "^4.0.0", "pathe": "^0.2.0" } } diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 82614cedd688..038c81a39c26 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -1,5 +1,6 @@ /* eslint-disable no-restricted-imports */ import { existsSync, promises as fs } from 'fs' +import pLimit from 'p-limit' import { relative, resolve } from 'pathe' import type { TransformPluginContext } from 'rollup' import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ResolvedCoverageOptions, Vitest } from 'vitest' @@ -23,7 +24,10 @@ interface TestExclude { exclude?: string | string[] extension?: string | string[] excludeNodeModules?: boolean - }): { shouldInstrument(filePath: string): boolean } + }): { + shouldInstrument(filePath: string): boolean + glob(cwd: string): Promise + } } export class IstanbulCoverageProvider implements CoverageProvider { @@ -95,12 +99,15 @@ export class IstanbulCoverageProvider implements CoverageProvider { } async reportCoverage() { - const mergedCoverage = this.coverages.reduce((coverage, previousCoverageMap) => { + const mergedCoverage: CoverageMap = this.coverages.reduce((coverage, previousCoverageMap) => { const map = libCoverage.createCoverageMap(coverage) map.merge(previousCoverageMap) return map }, {}) + if (this.options.all) + await this.includeUntestedFiles(mergedCoverage) + const sourceMapStore = libSourceMaps.createSourceMapStore() const coverageMap: CoverageMap = await sourceMapStore.transformCoverage(mergedCoverage) @@ -175,6 +182,42 @@ export class IstanbulCoverageProvider implements CoverageProvider { } } } + + async includeUntestedFiles(coverageMap: CoverageMap) { + // Load, instrument and collect empty coverages from all files which + // are not already in the coverage map + const includedFiles = await this.testExclude.glob(this.ctx.config.root) + const uncoveredFiles = includedFiles + .map(file => resolve(this.ctx.config.root, file)) + .filter(file => !coverageMap.data[file]) + + const limit = pLimit(50) + const transformUncoveredFiles = uncoveredFiles.map(filename => limit(async () => { + const transformResult = await this.ctx.vitenode.transformRequest(filename) + return { transformResult, filename } + })) + + const transformResults = await Promise.all(transformUncoveredFiles) + + for (const { transformResult, filename } of transformResults) { + const sourceMap = transformResult?.map + + if (sourceMap) { + this.instrumenter.instrumentSync( + transformResult.code, + filename, + { + ...sourceMap, + version: sourceMap.version.toString(), + }, + ) + + const lastCoverage = this.instrumenter.lastFileCoverage() + if (lastCoverage) + coverageMap.data[lastCoverage.path] = lastCoverage + } + } + } } function resolveIstanbulOptions(options: CoverageIstanbulOptions, root: string) { diff --git a/packages/vitest/src/types/coverage.ts b/packages/vitest/src/types/coverage.ts index d1adf01b48d2..f68fd07ed18f 100644 --- a/packages/vitest/src/types/coverage.ts +++ b/packages/vitest/src/types/coverage.ts @@ -137,6 +137,11 @@ export interface BaseCoverageOptions { * Extensions for files to be included in coverage */ extension?: string | string[] + + /** + * Whether to include all files, including the untested ones into report + */ + all?: boolean } export interface CoverageIstanbulOptions extends BaseCoverageOptions { @@ -166,7 +171,6 @@ export interface CoverageC8Options extends BaseCoverageOptions { */ excludeNodeModules?: boolean - all?: boolean src?: string[] 100?: boolean diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 0f607f609035..8092afb6c007 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -640,6 +640,7 @@ importers: istanbul-lib-report: ^3.0.0 istanbul-lib-source-maps: ^4.0.1 istanbul-reports: ^3.1.5 + p-limit: ^4.0.0 pathe: ^0.2.0 test-exclude: ^6.0.0 vitest: workspace:* @@ -657,6 +658,7 @@ importers: '@types/istanbul-lib-report': 3.0.0 '@types/istanbul-lib-source-maps': 4.0.1 '@types/istanbul-reports': 3.0.1 + p-limit: 4.0.0 pathe: 0.2.0 packages/ui: diff --git a/test/coverage-test/coverage-test/coverage.istanbul.test.ts b/test/coverage-test/coverage-test/coverage.istanbul.test.ts index c2188e16ec90..72dfddfbdcdc 100644 --- a/test/coverage-test/coverage-test/coverage.istanbul.test.ts +++ b/test/coverage-test/coverage-test/coverage.istanbul.test.ts @@ -3,7 +3,7 @@ import { resolve } from 'pathe' import { expect, test } from 'vitest' test('istanbul html report', async () => { - const coveragePath = resolve('./coverage') + const coveragePath = resolve('./coverage/coverage-test/src') const files = fs.readdirSync(coveragePath) expect(files).toContain('index.html') @@ -22,3 +22,10 @@ test('istanbul lcov report', async () => { expect(lcovReportFiles).toContain('index.html') }) + +test('all includes untested files', () => { + const coveragePath = resolve('./coverage/coverage-test/src') + const files = fs.readdirSync(coveragePath) + + expect(files).toContain('untested-file.ts.html') +}) diff --git a/test/coverage-test/src/untested-file.ts b/test/coverage-test/src/untested-file.ts new file mode 100644 index 000000000000..1d1313eef8d4 --- /dev/null +++ b/test/coverage-test/src/untested-file.ts @@ -0,0 +1,3 @@ +export default function untestedFile() { + return 'This file should end up in report when {"all": true} is given' +} diff --git a/test/coverage-test/vitest.config.ts b/test/coverage-test/vitest.config.ts index 3d4e9cf640f3..0570d7959b7b 100644 --- a/test/coverage-test/vitest.config.ts +++ b/test/coverage-test/vitest.config.ts @@ -19,6 +19,7 @@ export default defineConfig({ coverage: { enabled: true, clean: true, + all: true, reporter: ['html', 'text', 'lcov'], }, },