Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

refactor: internal coverage typings #2713

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
40 changes: 28 additions & 12 deletions packages/coverage-c8/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -47,7 +52,7 @@ export class C8CoverageProvider implements CoverageProvider {
async reportCoverage({ allTestsRun }: ReportContext = {}) {
takeCoverage()

const options = {
const options: ConstructorParameters<typeof Report>[0] = {
...this.options,
all: this.options.all && allTestsRun,
}
Expand Down Expand Up @@ -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']) {
Expand All @@ -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
}
28 changes: 17 additions & 11 deletions packages/coverage-istanbul/src/provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand All @@ -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 {
Expand All @@ -33,7 +35,7 @@ export class IstanbulCoverageProvider implements CoverageProvider {
name = 'istanbul'

ctx!: Vitest
options!: ResolvedCoverageOptions & CoverageIstanbulOptions & { provider: 'istanbul' }
options!: Options
instrumenter!: Instrumenter
testExclude!: InstanceType<TestExclude>

Expand Down Expand Up @@ -70,7 +72,7 @@ export class IstanbulCoverageProvider implements CoverageProvider {
})
}

resolveOptions(): ResolvedCoverageOptions {
resolveOptions() {
return this.options
}

Expand Down Expand Up @@ -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)
Expand Down Expand Up @@ -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
}

/**
Expand Down
2 changes: 1 addition & 1 deletion packages/vitest/src/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<UserConfig>
Expand Down
8 changes: 3 additions & 5 deletions packages/vitest/src/defaults.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
32 changes: 24 additions & 8 deletions packages/vitest/src/types/coverage.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,14 +53,30 @@ export type CoverageReporter =
| 'text-summary'
| 'text'

export type CoverageOptions =
| BaseCoverageOptions & { provider?: null | CoverageProviderModule }
| CoverageC8Options & { provider?: 'c8' }
| CoverageIstanbulOptions & { provider?: 'istanbul' }
Comment on lines -56 to -59
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Had to refactor this union type into a conditional type due to weird behaviour in TypeScript. Link to TS Playground

It seems that Typescript thinks a string may also extend the interface CoverageProviderModule.

type NarrowedProvider = "istanbul" | ({ method: () => string; } & "istanbul")


export type ResolvedCoverageOptions =
& { tempDirectory: string }
& Required<CoverageOptions>
type Provider = 'c8' | 'istanbul' | CoverageProviderModule | undefined

export type CoverageOptions<T extends Provider = Provider> =
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<T extends Provider = Provider> =
& CoverageOptions<T>
& Required<Pick<CoverageOptions<T>, FieldsWithDefaultValues>>
// Resolved fields which may have different typings as public configuration API has
& {
reporter: CoverageReporter[]
}

export interface BaseCoverageOptions {
/**
Expand Down
12 changes: 10 additions & 2 deletions test/coverage-test/test/configuration-options.test-d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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() {},
Expand Down