import type { AnalysisResult } from '../types';
export function htmlReport(result: AnalysisResult, rootDir: string): string {
const rel = (f: string) => f.replace(rootDir, '').replace(/\\/g, '/').replace(/^\//, '');
const langCards = result.detectedLanguages.map(l => {
const pct = l.definitionCount > 0 ? Math.round((l.deadCount / l.definitionCount) * 100) : 0;
return `
${cap(l.language)}
${l.fileCount} file${l.fileCount === 1 ? '' : 's'}
${l.deadCount} dead (${pct}%)
`;
}).join('');
const kindBars = Object.entries(result.cleanup.byKind)
.sort((a, b) => b[1] - a[1])
.map(([kind, count]) => {
const max = Math.max(...Object.values(result.cleanup.byKind));
const pct = Math.round((count / max) * 100);
return ``;
}).join('');
const rows = result.deadSymbols
.map(({ definition, reason }) =>
`
| ${escapeHtml(rel(definition.file))} |
${definition.line} |
${definition.kind} |
${escapeHtml(definition.name)} |
${definition.exported ? '✓' : '✗'} |
${escapeHtml(reason)} |
`
).join('\n');
return `
Dead Code Hunter Report
Dead Code Hunter
Generated ${new Date().toISOString().replace('T', ' ').slice(0, 19)} UTC
${result.scannedFiles}
Files Scanned
${result.deadSymbols.length}
Dead Symbols
~${result.cleanup.estimatedLines}
Est. Dead Lines
${result.durationMs}ms
Analysis Time
${result.detectedLanguages.length > 0 ? `Languages Detected
${langCards}
` : ''}
${result.deadSymbols.length > 0 ? `
Cleanup Potential
~${result.cleanup.estimatedLines} lines removable across ${result.byFile.size} files
${kindBars}
Dead Symbols
| File | Line | Kind | Name | Exported | Reason |
${rows}
` : 'No dead code found!
'}
`;
}
function escapeHtml(s: string): string {
return s.replace(/&/g, '&').replace(//g, '>').replace(/"/g, '"');
}
function cap(s: string): string {
return s.charAt(0).toUpperCase() + s.slice(1);
}