less
Version:
Leaner CSS
159 lines (137 loc) • 5.41 kB
JavaScript
/**
* Generates a per-file coverage report table for src/ directories
*/
const fs = require('fs');
const path = require('path');
const coverageSummaryPath = path.join(__dirname, '..', 'coverage', 'coverage-summary.json');
if (!fs.existsSync(coverageSummaryPath)) {
console.error('Coverage summary not found. Run pnpm test:coverage first.');
process.exit(1);
}
const coverage = JSON.parse(fs.readFileSync(coverageSummaryPath, 'utf8'));
// Filter to only src/ files (less, less-node) and bin/ files
// Note: src/less-browser/ is excluded because browser tests aren't included in coverage
// Abstract base classes are excluded as they're meant to be overridden by implementations
const abstractClasses = [
'abstract-file-manager',
'abstract-plugin-loader'
];
const srcFiles = Object.entries(coverage)
.filter(([filePath]) => {
const normalized = filePath.replace(/\\/g, '/');
// Exclude abstract classes
if (abstractClasses.some(abstract => normalized.includes(abstract))) {
return false;
}
return (normalized.includes('/src/less/') && !normalized.includes('/src/less-browser/')) ||
normalized.includes('/src/less-node/') ||
normalized.includes('/bin/');
})
.map(([filePath, data]) => {
// Extract relative path from absolute path
const normalized = filePath.replace(/\\/g, '/');
// Match src/ paths or bin/ paths
const match = normalized.match(/((?:src\/[^/]+\/[^/]+\/|bin\/).+)$/);
const relativePath = match ? match[1] : path.basename(filePath);
return {
path: relativePath,
statements: data.statements,
branches: data.branches,
functions: data.functions,
lines: data.lines
};
})
.sort((a, b) => {
// Sort by directory first, then by coverage percentage
const pathCompare = a.path.localeCompare(b.path);
if (pathCompare !== 0) return pathCompare;
return a.statements.pct - b.statements.pct;
});
if (srcFiles.length === 0) {
console.log('No src/ files found in coverage report.');
process.exit(0);
}
// Group by directory
const grouped = {
'src/less/': [],
'src/less-node/': [],
'bin/': []
};
srcFiles.forEach(file => {
if (file.path.startsWith('src/less/')) {
grouped['src/less/'].push(file);
} else if (file.path.startsWith('src/less-node/')) {
grouped['src/less-node/'].push(file);
} else if (file.path.startsWith('bin/')) {
grouped['bin/'].push(file);
}
});
// Print table
console.log('\n' + '='.repeat(100));
console.log('Per-File Coverage Report (src/less/, src/less-node/, and bin/)');
console.log('='.repeat(100));
console.log('For line-by-line coverage details, open coverage/index.html in your browser.');
console.log('='.repeat(100) + '\n');
Object.entries(grouped).forEach(([dir, files]) => {
if (files.length === 0) return;
console.log(`\n${dir.toUpperCase()}`);
console.log('-'.repeat(100));
console.log(
'File'.padEnd(50) +
'Statements'.padStart(12) +
'Branches'.padStart(12) +
'Functions'.padStart(12) +
'Lines'.padStart(12)
);
console.log('-'.repeat(100));
files.forEach(file => {
const filename = file.path.replace(dir, '');
const truncated = filename.length > 48 ? '...' + filename.slice(-45) : filename;
console.log(
truncated.padEnd(50) +
`${file.statements.pct.toFixed(1)}%`.padStart(12) +
`${file.branches.pct.toFixed(1)}%`.padStart(12) +
`${file.functions.pct.toFixed(1)}%`.padStart(12) +
`${file.lines.pct.toFixed(1)}%`.padStart(12)
);
});
// Summary for this directory
const totals = files.reduce((acc, file) => {
acc.statements.total += file.statements.total;
acc.statements.covered += file.statements.covered;
acc.branches.total += file.branches.total;
acc.branches.covered += file.branches.covered;
acc.functions.total += file.functions.total;
acc.functions.covered += file.functions.covered;
acc.lines.total += file.lines.total;
acc.lines.covered += file.lines.covered;
return acc;
}, {
statements: { total: 0, covered: 0 },
branches: { total: 0, covered: 0 },
functions: { total: 0, covered: 0 },
lines: { total: 0, covered: 0 }
});
const stmtPct = totals.statements.total > 0
? (totals.statements.covered / totals.statements.total * 100).toFixed(1)
: '0.0';
const branchPct = totals.branches.total > 0
? (totals.branches.covered / totals.branches.total * 100).toFixed(1)
: '0.0';
const funcPct = totals.functions.total > 0
? (totals.functions.covered / totals.functions.total * 100).toFixed(1)
: '0.0';
const linePct = totals.lines.total > 0
? (totals.lines.covered / totals.lines.total * 100).toFixed(1)
: '0.0';
console.log('-'.repeat(100));
console.log(
'TOTAL'.padEnd(50) +
`${stmtPct}%`.padStart(12) +
`${branchPct}%`.padStart(12) +
`${funcPct}%`.padStart(12) +
`${linePct}%`.padStart(12)
);
});
console.log('\n' + '='.repeat(100) + '\n');