Skip to content

Commit 40cabb1

Browse files
committed
feat(coverage): filter dist and external files from coverage reports
Implement post-processing to exclude non-source files from coverage: - Create filter-coverage.mjs script with logger integration - Filter out 33 dist/ compiled files, 3 external dependencies, 2 test files - Keep only 111 src/ TypeScript files for accurate coverage measurement - Integrate filtering into cover.mjs workflow automatically - Update vitest.config.mts with improved exclusion patterns This provides true source-only coverage metrics by removing: - Compiled JavaScript from dist/ - Bundled external dependencies (36MB of code) - Test helper files Coverage reports (HTML/LCOV) now accurately reflect src/ TypeScript coverage only.
1 parent d69f1cb commit 40cabb1

File tree

3 files changed

+118
-25
lines changed

3 files changed

+118
-25
lines changed

.config/vitest.config.mts

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,13 +132,21 @@ export default defineConfig({
132132
'test/**',
133133
'packages/**',
134134
'perf/**',
135-
'**/dist/**',
135+
// Exclude all dist directory and its contents
136136
'dist/**',
137+
'**/dist/**',
138+
'**/{dist,build,out}/**',
139+
// Exclude external bundled dependencies
137140
'src/external/**',
141+
'**/external/**',
138142
'src/types.ts',
139143
'scripts/**',
140144
],
141-
include: ['src/**/*.{ts,mts,cts}'],
145+
include: [
146+
'src/**/*.{ts,mts,cts}',
147+
// Explicitly exclude external from include
148+
'!src/external/**',
149+
],
142150
excludeAfterRemap: true,
143151
all: true,
144152
clean: true,

scripts/cover.mjs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -218,6 +218,26 @@ try {
218218
}
219219
}
220220

221+
// Filter coverage data to exclude dist/ and external files
222+
if (exitCode === 0) {
223+
logger.info('Filtering coverage data to src/ files only...')
224+
try {
225+
const filterResult = await spawn(
226+
'node',
227+
['scripts/filter-coverage.mjs'],
228+
{
229+
cwd: rootPath,
230+
stdio: 'inherit',
231+
},
232+
)
233+
if (filterResult.code !== 0) {
234+
logger.warn('Coverage filtering had issues but continuing...')
235+
}
236+
} catch (filterError) {
237+
logger.warn(`Coverage filtering failed: ${filterError.message}`)
238+
}
239+
}
240+
221241
if (exitCode === 0) {
222242
logger.success('Coverage completed successfully')
223243
} else {

scripts/filter-coverage.mjs

Lines changed: 88 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,108 @@
11
/**
2-
* @fileoverview Filter coverage data to exclude dist/ files
2+
* @fileoverview Filter coverage data to exclude dist/ and external files
33
*
4-
* This script post-processes V8 coverage data to remove dist/ files,
5-
* ensuring coverage reports only show src/ TypeScript files.
4+
* This script post-processes V8 coverage data to remove:
5+
* - dist/ compiled JavaScript files
6+
* - external bundled dependencies
7+
* - test files
8+
* Ensuring coverage reports only show src/ TypeScript files (excluding src/external).
69
*/
710

811
import fs from 'node:fs'
912
import path from 'node:path'
1013
import { fileURLToPath } from 'node:url'
14+
import { getDefaultLogger } from '../dist/logger.js'
1115

16+
const logger = getDefaultLogger()
1217
const __dirname = path.dirname(fileURLToPath(import.meta.url))
1318
const projectRoot = path.resolve(__dirname, '..')
14-
const coveragePath = path.join(projectRoot, 'coverage/coverage-final.json')
1519

16-
if (!fs.existsSync(coveragePath)) {
17-
console.error('Coverage file not found:', coveragePath)
20+
// Find all coverage JSON files
21+
const coverageDir = path.join(projectRoot, 'coverage')
22+
if (!fs.existsSync(coverageDir)) {
23+
logger.error('Coverage directory not found:', coverageDir)
1824
process.exit(1)
1925
}
2026

21-
const coverage = JSON.parse(fs.readFileSync(coveragePath, 'utf-8'))
27+
const coverageFinalPath = path.join(coverageDir, 'coverage-final.json')
28+
const coverageSummaryPath = path.join(coverageDir, 'coverage-summary.json')
2229

23-
// Filter out dist/ files
24-
const filtered = {}
25-
let distCount = 0
26-
let srcCount = 0
30+
function filterCoverageFile(filePath) {
31+
if (!fs.existsSync(filePath)) {
32+
logger.info(`Skipping ${path.basename(filePath)} - not found`)
33+
return { filtered: 0, kept: 0, total: 0, details: {} }
34+
}
35+
36+
const coverage = JSON.parse(fs.readFileSync(filePath, 'utf-8'))
37+
const filtered = {}
38+
let distCount = 0
39+
let externalCount = 0
40+
let testCount = 0
41+
let srcCount = 0
42+
43+
for (const [file, data] of Object.entries(coverage)) {
44+
// Exclude dist/ compiled files
45+
if (file.includes('/dist/') || file.includes('\\dist\\')) {
46+
distCount++
47+
continue
48+
}
49+
50+
// Exclude external bundled dependencies
51+
if (
52+
file.includes('/external/') ||
53+
file.includes('\\external\\') ||
54+
file.includes('src/external')
55+
) {
56+
externalCount++
57+
continue
58+
}
59+
60+
// Exclude test files
61+
if (file.includes('/test/') || file.includes('\\test\\')) {
62+
testCount++
63+
continue
64+
}
65+
66+
// Keep src/ TypeScript files
67+
if (file.includes('/src/') || file.includes('\\src\\')) {
68+
filtered[file] = data
69+
srcCount++
70+
}
71+
}
72+
73+
fs.writeFileSync(filePath, JSON.stringify(filtered, null, 2))
2774

28-
for (const [file, data] of Object.entries(coverage)) {
29-
// Keep only src/ files, exclude dist/
30-
if (file.includes('/src/') && !file.includes('/dist/')) {
31-
filtered[file] = data
32-
srcCount++
33-
} else if (file.includes('/dist/')) {
34-
distCount++
75+
return {
76+
filtered: distCount + externalCount + testCount,
77+
kept: srcCount,
78+
total: Object.keys(coverage).length,
79+
details: { distCount, externalCount, testCount, srcCount },
3580
}
3681
}
3782

38-
console.log(`Filtered ${distCount} dist/ files`)
39-
console.log(`Kept ${srcCount} src/ files`)
40-
console.log(`Total files: ${Object.keys(coverage).length}`)
83+
logger.info('Filtering coverage data...\n')
84+
85+
const finalStats = filterCoverageFile(coverageFinalPath)
86+
logger.info(`coverage-final.json:`)
87+
logger.success(` Kept ${finalStats.kept} src/ TypeScript files`)
88+
if (finalStats.filtered > 0) {
89+
logger.info(` Filtered ${finalStats.filtered} files:`)
90+
if (finalStats.details.distCount)
91+
logger.info(` - ${finalStats.details.distCount} dist/ compiled files`)
92+
if (finalStats.details.externalCount)
93+
logger.info(
94+
` - ${finalStats.details.externalCount} external dependencies`,
95+
)
96+
if (finalStats.details.testCount)
97+
logger.info(` - ${finalStats.details.testCount} test files`)
98+
}
99+
logger.info(` Total: ${finalStats.total} files\n`)
100+
101+
const summaryStats = filterCoverageFile(coverageSummaryPath)
102+
logger.info(`coverage-summary.json:`)
103+
logger.success(` Kept ${summaryStats.kept} src/ files`)
104+
if (summaryStats.filtered > 0) {
105+
logger.info(` Filtered ${summaryStats.filtered} files`)
106+
}
41107

42-
fs.writeFileSync(coveragePath, JSON.stringify(filtered, null, 2))
43-
console.log('Coverage data filtered successfully')
108+
logger.success('\n✓ Coverage data filtered successfully!')

0 commit comments

Comments
 (0)