Comprehensive JavaScript code coverage tool that computes statement, line, function and branch coverage with module loader hooks for transparent instrumentation
—
Pending
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Pending
The risk profile of this skill
Coverage utilities process and manipulate coverage objects including merging, summarization, format conversion, and derived metric calculation. These functions work with the raw coverage data structures generated by instrumented code.
Process coverage objects to add derived information and calculate metrics.
const utils = {
/**
* Adds line coverage information to all file coverage objects
* @param {Object} coverage - Coverage object containing file coverage data
*/
addDerivedInfo(coverage: Object): void;
/**
* Adds line coverage information to a single file coverage object
* @param {Object} fileCoverage - Single file coverage object
*/
addDerivedInfoForFile(fileCoverage: Object): void;
/**
* Removes line coverage information from coverage objects
* @param {Object} coverage - Coverage object to clean
*/
removeDerivedInfo(coverage: Object): void;
/**
* Returns a blank summary metrics object
* @returns {Object} Empty summary with zero counts
*/
blankSummary(): Object;
/**
* Returns summary metrics for a single file coverage object
* @param {Object} fileCoverage - File coverage object
* @returns {Object} Summary metrics (totals, covered, skipped, percentages)
*/
summarizeFileCoverage(fileCoverage: Object): Object;
/**
* Returns summary metrics for entire coverage object
* @param {Object} coverage - Complete coverage object
* @returns {Object} Aggregated summary metrics
*/
summarizeCoverage(coverage: Object): Object;
/**
* Merges two file coverage objects
* @param {Object} first - First file coverage object
* @param {Object} second - Second file coverage object
* @returns {Object} Merged file coverage object
*/
mergeFileCoverage(first: Object, second: Object): Object;
/**
* Merges multiple summary metrics objects
* @param {...Object} summaries - Summary objects to merge
* @returns {Object} Merged summary metrics
*/
mergeSummaryObjects(...summaries: Object[]): Object;
/**
* Converts coverage object to YUI Test coverage format
* @param {Object} coverage - Istanbul coverage object
* @returns {Object} YUI Test coverage format
*/
toYUICoverage(coverage: Object): Object;
/**
* Increments hit counts on items marked as ignored/skipped
* @param {Object} cov - File coverage object to process
* @returns {Object} New file coverage object with incremented ignored totals
*/
incrementIgnoredTotals(cov: Object): Object;
};Usage Examples:
const { utils } = require('istanbul');
// Process coverage data after collection
const collector = new Collector();
collector.add(global.__coverage__);
const coverage = collector.getFinalCoverage();
// Add line coverage information (derived from statements)
utils.addDerivedInfo(coverage);
// Get summary metrics
const summary = utils.summarizeCoverage(coverage);
console.log('Overall coverage:', summary);
// Get summary for individual files
Object.keys(coverage).forEach(filename => {
const fileSummary = utils.summarizeFileCoverage(coverage[filename]);
console.log(`${filename}: ${fileSummary.statements.pct}% statements`);
});Summary objects returned by utility functions contain comprehensive coverage metrics:
interface SummaryMetrics {
/** Statement coverage metrics */
statements: CoverageMetric;
/** Branch coverage metrics */
branches: CoverageMetric;
/** Function coverage metrics */
functions: CoverageMetric;
/** Line coverage metrics */
lines: CoverageMetric;
}
interface CoverageMetric {
/** Total number of items */
total: number;
/** Number of covered items */
covered: number;
/** Number of skipped/ignored items */
skipped: number;
/** Coverage percentage (0-100) */
pct: number;
}Example Summary Output:
const summary = utils.summarizeCoverage(coverage);
console.log(JSON.stringify(summary, null, 2));
// Output:
{
"statements": {
"total": 45,
"covered": 38,
"skipped": 0,
"pct": 84.44
},
"branches": {
"total": 12,
"covered": 9,
"skipped": 0,
"pct": 75
},
"functions": {
"total": 8,
"covered": 7,
"skipped": 0,
"pct": 87.5
},
"lines": {
"total": 42,
"covered": 35,
"skipped": 0,
"pct": 83.33
}
}Merge coverage data from multiple sources or test runs:
// Merge file coverage objects
const merged = utils.mergeFileCoverage(coverage1['app.js'], coverage2['app.js']);
// Example: coverage from two test runs
const testRun1 = {
s: { '1': 1, '2': 0, '3': 1 }, // statements
b: { '1': [1, 0] }, // branches
f: { '1': 1, '2': 0 } // functions
};
const testRun2 = {
s: { '1': 1, '2': 1, '3': 1 },
b: { '1': [1, 1] },
f: { '1': 1, '2': 1 }
};
const mergedFile = utils.mergeFileCoverage(testRun1, testRun2);
// Result: { s: { '1': 2, '2': 1, '3': 2 }, b: { '1': [2, 1] }, f: { '1': 2, '2': 1 } }
// Merge summary objects
const summary1 = utils.summarizeFileCoverage(coverage1['file1.js']);
const summary2 = utils.summarizeFileCoverage(coverage2['file2.js']);
const combinedSummary = utils.mergeSummaryObjects(summary1, summary2);Add line coverage information derived from statement coverage:
// Coverage object before adding derived info
const fileCoverage = {
path: 'app.js',
s: { '1': 1, '2': 0, '3': 1 },
statementMap: {
'1': { start: { line: 5, column: 0 }, end: { line: 5, column: 20 } },
'2': { start: { line: 8, column: 4 }, end: { line: 8, column: 15 } },
'3': { start: { line: 12, column: 0 }, end: { line: 12, column: 25 } }
}
// ... other coverage data
};
// Add line coverage (modifies object in place)
utils.addDerivedInfoForFile(fileCoverage);
// Now includes line coverage derived from statements
console.log(fileCoverage.l);
// Output: { '5': 1, '8': 0, '12': 1 }
// Remove derived info if needed
utils.removeDerivedInfo({ 'app.js': fileCoverage });Convert between Istanbul and other coverage formats:
// Convert to YUI Test format
const yuiCoverage = utils.toYUICoverage(coverage);
// YUI format is different structure optimized for YUI Test
console.log('YUI coverage format:', yuiCoverage);
// Example conversion
const istanbulCoverage = {
'app.js': {
path: 'app.js',
s: { '1': 1, '2': 0 },
b: { '1': [1, 0] },
f: { '1': 1 }
// ... other data
}
};
const yui = utils.toYUICoverage(istanbulCoverage);
// Converts to YUI Test's expected coverage object structurefunction processCoverageData(coverageFiles) {
const allCoverage = {};
// Merge all coverage files
coverageFiles.forEach(file => {
const coverage = require(file);
Object.keys(coverage).forEach(filename => {
if (allCoverage[filename]) {
// Merge with existing
allCoverage[filename] = utils.mergeFileCoverage(
allCoverage[filename],
coverage[filename]
);
} else {
// First occurrence
allCoverage[filename] = coverage[filename];
}
});
});
// Add derived information
utils.addDerivedInfo(allCoverage);
// Generate summary
const summary = utils.summarizeCoverage(allCoverage);
return {
coverage: allCoverage,
summary: summary
};
}function analyzeCoverageQuality(coverage) {
utils.addDerivedInfo(coverage);
const fileAnalysis = [];
Object.keys(coverage).forEach(filename => {
const fileCoverage = coverage[filename];
const summary = utils.summarizeFileCoverage(fileCoverage);
fileAnalysis.push({
file: filename,
statements: summary.statements.pct,
branches: summary.branches.pct,
functions: summary.functions.pct,
lines: summary.lines.pct,
issues: []
});
// Identify coverage issues
const analysis = fileAnalysis[fileAnalysis.length - 1];
if (summary.statements.pct < 80) {
analysis.issues.push('Low statement coverage');
}
if (summary.branches.pct < 70) {
analysis.issues.push('Low branch coverage');
}
if (summary.functions.pct < 90) {
analysis.issues.push('Low function coverage');
}
});
return fileAnalysis;
}function generateCustomSummary(coverage) {
const blank = utils.blankSummary();
const files = Object.keys(coverage);
// Generate summary for each directory
const directorySummaries = {};
files.forEach(filename => {
const directory = path.dirname(filename);
if (!directorySummaries[directory]) {
directorySummaries[directory] = utils.blankSummary();
}
const fileSummary = utils.summarizeFileCoverage(coverage[filename]);
directorySummaries[directory] = utils.mergeSummaryObjects(
directorySummaries[directory],
fileSummary
);
});
// Overall summary
const overallSummary = utils.summarizeCoverage(coverage);
return {
overall: overallSummary,
directories: directorySummaries,
files: files.map(f => ({
name: f,
summary: utils.summarizeFileCoverage(coverage[f])
}))
};
}function checkCoverageThresholds(coverage, thresholds) {
const summary = utils.summarizeCoverage(coverage);
const results = {};
['statements', 'branches', 'functions', 'lines'].forEach(metric => {
const threshold = thresholds[metric];
const actual = summary[metric].pct;
results[metric] = {
threshold: threshold,
actual: actual,
passed: actual >= threshold,
difference: actual - threshold
};
});
return results;
}
// Usage
const thresholds = {
statements: 80,
branches: 75,
functions: 85,
lines: 80
};
const results = checkCoverageThresholds(coverage, thresholds);
console.log('Threshold results:', results);
// Check if all thresholds passed
const allPassed = Object.values(results).every(r => r.passed);
if (!allPassed) {
console.error('Coverage thresholds not met');
process.exit(1);
}For large coverage objects, optimize processing:
// Process coverage in chunks for large datasets
function processLargeCoverage(coverage, chunkSize = 100) {
const files = Object.keys(coverage);
const results = [];
for (let i = 0; i < files.length; i += chunkSize) {
const chunk = files.slice(i, i + chunkSize);
const chunkCoverage = {};
chunk.forEach(file => {
chunkCoverage[file] = coverage[file];
});
// Process chunk
utils.addDerivedInfo(chunkCoverage);
const chunkSummary = utils.summarizeCoverage(chunkCoverage);
results.push(chunkSummary);
// Allow event loop to process other tasks
if (i % (chunkSize * 10) === 0) {
await new Promise(resolve => setImmediate(resolve));
}
}
// Merge all chunk summaries
return utils.mergeSummaryObjects(...results);
}Handle malformed coverage data:
function safeCoverageProcessing(coverage) {
try {
// Validate coverage structure
if (typeof coverage !== 'object' || coverage === null) {
throw new Error('Invalid coverage object');
}
// Process each file safely
Object.keys(coverage).forEach(filename => {
const fileCoverage = coverage[filename];
if (!fileCoverage.s || !fileCoverage.b || !fileCoverage.f) {
console.warn(`Incomplete coverage data for ${filename}`);
return;
}
try {
utils.addDerivedInfoForFile(fileCoverage);
} catch (error) {
console.warn(`Failed to process ${filename}:`, error.message);
}
});
return utils.summarizeCoverage(coverage);
} catch (error) {
console.error('Coverage processing failed:', error.message);
return utils.blankSummary();
}
}