diff --git a/packages/coverage-c8/src/provider.ts b/packages/coverage-c8/src/provider.ts index 8972c22a65ef..7de1b28af7a6 100644 --- a/packages/coverage-c8/src/provider.ts +++ b/packages/coverage-c8/src/provider.ts @@ -4,20 +4,25 @@ import type { Profiler } from 'inspector' import { takeCoverage } from 'v8' import { extname, resolve } from 'pathe' import type { RawSourceMap } from 'vite-node' -import { configDefaults } from 'vitest/config' +import { coverageConfigDefaults } from 'vitest/config' // eslint-disable-next-line no-restricted-imports import type { CoverageC8Options, CoverageProvider, ReportContext, ResolvedCoverageOptions } from 'vitest' import type { Vitest } from 'vitest/node' +import type { Report } from 'c8' // @ts-expect-error missing types import createReport from 'c8/lib/report.js' // @ts-expect-error missing types import { checkCoverages } from 'c8/lib/commands/check-coverage.js' +type Options = + & ResolvedCoverageOptions<'c8'> + & { tempDirectory: string } + export class C8CoverageProvider implements CoverageProvider { name = 'c8' ctx!: Vitest - options!: ResolvedCoverageOptions & { provider: 'c8' } + options!: Options initialize(ctx: Vitest) { this.ctx = ctx @@ -47,7 +52,7 @@ export class C8CoverageProvider implements CoverageProvider { async reportCoverage({ allTestsRun }: ReportContext = {}) { takeCoverage() - const options = { + const options: ConstructorParameters[0] = { ...this.options, all: this.options.all && allTestsRun, } @@ -151,10 +156,26 @@ export class C8CoverageProvider implements CoverageProvider { await fs.rm(this.options.tempDirectory, { recursive: true, force: true, maxRetries: 10 }) } } -function resolveC8Options(options: CoverageC8Options, root: string) { - const resolved = { - ...configDefaults.coverage, - ...options as any, + +function resolveC8Options(options: CoverageC8Options, root: string): Options { + const reportsDirectory = resolve(root, options.reportsDirectory || coverageConfigDefaults.reportsDirectory) + const reporter = options.reporter || coverageConfigDefaults.reporter + + const resolved: Options = { + ...coverageConfigDefaults, + + // Provider specific defaults + excludeNodeModules: true, + allowExternal: false, + + // User's options + ...options, + + // Resolved fields + provider: 'c8', + tempDirectory: process.env.NODE_V8_COVERAGE || resolve(reportsDirectory, 'tmp'), + reporter: Array.isArray(reporter) ? reporter : [reporter], + reportsDirectory, } if (options['100']) { @@ -164,10 +185,5 @@ function resolveC8Options(options: CoverageC8Options, root: string) { resolved.statements = 100 } - resolved.reporter = resolved.reporter || [] - resolved.reporter = Array.isArray(resolved.reporter) ? resolved.reporter : [resolved.reporter] - resolved.reportsDirectory = resolve(root, resolved.reportsDirectory) - resolved.tempDirectory = process.env.NODE_V8_COVERAGE || resolve(resolved.reportsDirectory, 'tmp') - return resolved } diff --git a/packages/coverage-istanbul/src/provider.ts b/packages/coverage-istanbul/src/provider.ts index 42259dd62867..377c64ea3d15 100644 --- a/packages/coverage-istanbul/src/provider.ts +++ b/packages/coverage-istanbul/src/provider.ts @@ -3,7 +3,7 @@ import { existsSync, promises as fs } from 'fs' import { relative, resolve } from 'pathe' import type { TransformPluginContext } from 'rollup' import type { AfterSuiteRunMeta, CoverageIstanbulOptions, CoverageProvider, ReportContext, ResolvedCoverageOptions, Vitest } from 'vitest' -import { configDefaults, defaultExclude, defaultInclude } from 'vitest/config' +import { coverageConfigDefaults, defaultExclude, defaultInclude } from 'vitest/config' import libReport from 'istanbul-lib-report' import reports from 'istanbul-reports' import type { CoverageMap } from 'istanbul-lib-coverage' @@ -14,6 +14,8 @@ import { type Instrumenter, createInstrumenter } from 'istanbul-lib-instrument' import _TestExclude from 'test-exclude' import { COVERAGE_STORE_KEY } from './constants' +type Options = ResolvedCoverageOptions<'istanbul'> + type Threshold = 'lines' | 'functions' | 'statements' | 'branches' interface TestExclude { @@ -33,7 +35,7 @@ export class IstanbulCoverageProvider implements CoverageProvider { name = 'istanbul' ctx!: Vitest - options!: ResolvedCoverageOptions & CoverageIstanbulOptions & { provider: 'istanbul' } + options!: Options instrumenter!: Instrumenter testExclude!: InstanceType @@ -70,7 +72,7 @@ export class IstanbulCoverageProvider implements CoverageProvider { }) } - resolveOptions(): ResolvedCoverageOptions { + resolveOptions() { return this.options } @@ -121,7 +123,7 @@ export class IstanbulCoverageProvider implements CoverageProvider { }) for (const reporter of this.options.reporter) { - reports.create(reporter as any, { + reports.create(reporter, { skipFull: this.options.skipFull, projectRoot: this.ctx.config.root, }).execute(context) @@ -217,19 +219,23 @@ export class IstanbulCoverageProvider implements CoverageProvider { } } -function resolveIstanbulOptions(options: CoverageIstanbulOptions, root: string) { - const reportsDirectory = resolve(root, options.reportsDirectory || configDefaults.coverage.reportsDirectory!) +function resolveIstanbulOptions(options: CoverageIstanbulOptions, root: string): Options { + const reportsDirectory = resolve(root, options.reportsDirectory || coverageConfigDefaults.reportsDirectory) + const reporter = options.reporter || coverageConfigDefaults.reporter + + const resolved: Options = { + ...coverageConfigDefaults, - const resolved = { - ...configDefaults.coverage, + // User's options ...options, + + // Resolved fields provider: 'istanbul', reportsDirectory, - tempDirectory: resolve(reportsDirectory, 'tmp'), - reporter: Array.isArray(options.reporter) ? options.reporter : [options.reporter], + reporter: Array.isArray(reporter) ? reporter : [reporter], } - return resolved as ResolvedCoverageOptions & { provider: 'istanbul' } + return resolved } /** diff --git a/packages/vitest/src/config.ts b/packages/vitest/src/config.ts index dc18055c82e0..91ea018bf49f 100644 --- a/packages/vitest/src/config.ts +++ b/packages/vitest/src/config.ts @@ -5,7 +5,7 @@ export interface UserConfig extends ViteUserConfig { } // will import vitest declare test in module 'vite' -export { configDefaults, defaultInclude, defaultExclude } from './defaults' +export { configDefaults, defaultInclude, defaultExclude, coverageConfigDefaults } from './defaults' export type { ConfigEnv } export type UserConfigFn = (env: ConfigEnv) => UserConfig | Promise diff --git a/packages/vitest/src/defaults.ts b/packages/vitest/src/defaults.ts index f626976533c8..4c494df06c4a 100644 --- a/packages/vitest/src/defaults.ts +++ b/packages/vitest/src/defaults.ts @@ -24,21 +24,19 @@ const defaultCoverageExcludes = [ '**/.{eslint,mocha,prettier}rc.{js,cjs,yml}', ] -const coverageConfigDefaults = { - all: false, +// These are the generic defaults for coverage. Providers may also set some provider speficic defaults. +export const coverageConfigDefaults: ResolvedCoverageOptions = { provider: 'c8', enabled: false, clean: true, cleanOnRerun: true, reportsDirectory: './coverage', - excludeNodeModules: true, exclude: defaultCoverageExcludes, reporter: ['text', 'html', 'clover', 'json'], - allowExternal: false, // default extensions used by c8, plus '.vue' and '.svelte' // see https://github.com/istanbuljs/schema/blob/master/default-extension.js extension: ['.js', '.cjs', '.mjs', '.ts', '.mts', '.cts', '.tsx', '.jsx', '.vue', '.svelte'], -} as ResolvedCoverageOptions +} export const fakeTimersDefaults = { loopLimit: 10_000, diff --git a/packages/vitest/src/types/coverage.ts b/packages/vitest/src/types/coverage.ts index a0ca8dc198b2..209f274ec9e1 100644 --- a/packages/vitest/src/types/coverage.ts +++ b/packages/vitest/src/types/coverage.ts @@ -53,14 +53,30 @@ export type CoverageReporter = | 'text-summary' | 'text' -export type CoverageOptions = - | BaseCoverageOptions & { provider?: null | CoverageProviderModule } - | CoverageC8Options & { provider?: 'c8' } - | CoverageIstanbulOptions & { provider?: 'istanbul' } - -export type ResolvedCoverageOptions = - & { tempDirectory: string } - & Required +type Provider = 'c8' | 'istanbul' | CoverageProviderModule | undefined + +export type CoverageOptions = + T extends CoverageProviderModule ? ({ provider: T } & BaseCoverageOptions) : + T extends 'istanbul' ? ({ provider: T } & CoverageIstanbulOptions) : + ({ provider?: T } & CoverageC8Options) + +/** Fields that have default values. Internally these will always be defined. */ +type FieldsWithDefaultValues = + | 'enabled' + | 'clean' + | 'cleanOnRerun' + | 'reportsDirectory' + | 'exclude' + | 'extension' + | 'reporter' + +export type ResolvedCoverageOptions = + & CoverageOptions + & Required, FieldsWithDefaultValues>> + // Resolved fields which may have different typings as public configuration API has + & { + reporter: CoverageReporter[] + } export interface BaseCoverageOptions { /** diff --git a/test/coverage-test/test/configuration-options.test-d.ts b/test/coverage-test/test/configuration-options.test-d.ts index 8bcac13da09d..025ef4155d50 100644 --- a/test/coverage-test/test/configuration-options.test-d.ts +++ b/test/coverage-test/test/configuration-options.test-d.ts @@ -21,8 +21,16 @@ test('providers, custom', () => { return { name: 'custom-provider', initialize(_: Vitest) {}, - resolveOptions() { - return {} as ResolvedCoverageOptions + resolveOptions(): ResolvedCoverageOptions { + return { + clean: true, + cleanOnRerun: true, + enabled: true, + exclude: ['string'], + extension: ['string'], + reporter: ['html', 'json'], + reportsDirectory: 'string', + } }, clean(_: boolean) {}, onBeforeFilesRun() {},