288 lines
No EOL
8.6 KiB
TypeScript
288 lines
No EOL
8.6 KiB
TypeScript
import type {
|
|
CoverageConfig,
|
|
CoverageReport,
|
|
PackageCoverage,
|
|
CoverageMetric,
|
|
FileCoverage
|
|
} from './types';
|
|
|
|
export class CoverageProcessor {
|
|
constructor(private config: CoverageConfig) {}
|
|
|
|
process(rawCoverage: any, testResults: any[]): CoverageReport {
|
|
// Use the package information from raw coverage if available
|
|
const packages = rawCoverage.packages
|
|
? this.processPackagesCoverage(rawCoverage.packages)
|
|
: this.groupByPackage(rawCoverage.files, testResults);
|
|
|
|
const overall = this.calculateOverallCoverage(packages);
|
|
|
|
return {
|
|
timestamp: new Date().toISOString(),
|
|
packages,
|
|
overall,
|
|
config: this.config,
|
|
};
|
|
}
|
|
|
|
private processPackagesCoverage(packagesData: any): PackageCoverage[] {
|
|
const packages: PackageCoverage[] = [];
|
|
|
|
for (const [packageName, packageData] of Object.entries(packagesData)) {
|
|
if (!packageData || typeof packageData !== 'object') continue;
|
|
|
|
const pkg: PackageCoverage = {
|
|
name: packageName,
|
|
path: '', // Will be set from files if available
|
|
lines: this.createMetricFromRaw(packageData.lines),
|
|
functions: this.createMetricFromRaw(packageData.functions),
|
|
branches: this.createMetricFromRaw(packageData.branches),
|
|
statements: this.createMetricFromRaw(packageData.lines), // Often same as lines
|
|
files: [],
|
|
};
|
|
|
|
// Process files if available
|
|
if (packageData.files && Array.isArray(packageData.files)) {
|
|
for (const file of packageData.files) {
|
|
const fileCoverage = this.processFile(file);
|
|
pkg.files.push(fileCoverage);
|
|
|
|
// Set package path from first file if not set
|
|
if (!pkg.path && file.path) {
|
|
pkg.path = this.getPackagePath(file.path);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Only include packages that have files with coverage data
|
|
if (pkg.files.length > 0) {
|
|
packages.push(pkg);
|
|
}
|
|
}
|
|
|
|
return packages;
|
|
}
|
|
|
|
private createMetricFromRaw(rawMetric: any): CoverageMetric {
|
|
if (!rawMetric || typeof rawMetric !== 'object') {
|
|
return this.createEmptyMetric();
|
|
}
|
|
|
|
const total = rawMetric.found || 0;
|
|
const covered = rawMetric.hit || 0;
|
|
|
|
return {
|
|
total,
|
|
covered,
|
|
skipped: 0,
|
|
percentage: total > 0 ? (covered / total) * 100 : 100,
|
|
};
|
|
}
|
|
|
|
private groupByPackage(files: any[], testResults: any[]): PackageCoverage[] {
|
|
const packageMap = new Map<string, PackageCoverage>();
|
|
|
|
// Group files by package
|
|
for (const file of files) {
|
|
const packageName = this.getPackageFromPath(file.path);
|
|
|
|
if (!packageMap.has(packageName)) {
|
|
packageMap.set(packageName, {
|
|
name: packageName,
|
|
path: this.getPackagePath(file.path),
|
|
lines: this.createEmptyMetric(),
|
|
functions: this.createEmptyMetric(),
|
|
branches: this.createEmptyMetric(),
|
|
statements: this.createEmptyMetric(),
|
|
files: [],
|
|
});
|
|
}
|
|
|
|
const pkg = packageMap.get(packageName)!;
|
|
const fileCoverage = this.processFile(file);
|
|
|
|
pkg.files.push(fileCoverage);
|
|
this.addMetrics(pkg.lines, fileCoverage.lines);
|
|
this.addMetrics(pkg.functions, fileCoverage.functions);
|
|
this.addMetrics(pkg.branches, fileCoverage.branches);
|
|
this.addMetrics(pkg.statements, fileCoverage.statements);
|
|
}
|
|
|
|
// Calculate percentages for each package
|
|
const packages = Array.from(packageMap.values());
|
|
for (const pkg of packages) {
|
|
this.calculatePercentage(pkg.lines);
|
|
this.calculatePercentage(pkg.functions);
|
|
this.calculatePercentage(pkg.branches);
|
|
this.calculatePercentage(pkg.statements);
|
|
}
|
|
|
|
return packages;
|
|
}
|
|
|
|
private processFile(file: any): FileCoverage {
|
|
const lines = this.createMetric(file.lines.found, file.lines.hit);
|
|
const functions = this.createMetric(file.functions.found, file.functions.hit);
|
|
const branches = this.createMetric(file.branches.found, file.branches.hit);
|
|
// Statements often equal lines in simple coverage tools
|
|
const statements = this.createMetric(file.lines.found, file.lines.hit);
|
|
|
|
return {
|
|
path: file.path,
|
|
lines,
|
|
functions,
|
|
branches,
|
|
statements,
|
|
};
|
|
}
|
|
|
|
private createEmptyMetric(): CoverageMetric {
|
|
return {
|
|
total: 0,
|
|
covered: 0,
|
|
skipped: 0,
|
|
percentage: 0,
|
|
};
|
|
}
|
|
|
|
private createMetric(total: number, covered: number): CoverageMetric {
|
|
return {
|
|
total,
|
|
covered,
|
|
skipped: 0,
|
|
percentage: total > 0 ? (covered / total) * 100 : 100,
|
|
};
|
|
}
|
|
|
|
private addMetrics(target: CoverageMetric, source: CoverageMetric): void {
|
|
target.total += source.total;
|
|
target.covered += source.covered;
|
|
target.skipped += source.skipped;
|
|
}
|
|
|
|
private calculatePercentage(metric: CoverageMetric): void {
|
|
metric.percentage = metric.total > 0 ? (metric.covered / metric.total) * 100 : 100;
|
|
}
|
|
|
|
private calculateOverallCoverage(packages: PackageCoverage[]): CoverageReport['overall'] {
|
|
const overall = {
|
|
lines: this.createEmptyMetric(),
|
|
functions: this.createEmptyMetric(),
|
|
branches: this.createEmptyMetric(),
|
|
statements: this.createEmptyMetric(),
|
|
};
|
|
|
|
for (const pkg of packages) {
|
|
this.addMetrics(overall.lines, pkg.lines);
|
|
this.addMetrics(overall.functions, pkg.functions);
|
|
this.addMetrics(overall.branches, pkg.branches);
|
|
this.addMetrics(overall.statements, pkg.statements);
|
|
}
|
|
|
|
this.calculatePercentage(overall.lines);
|
|
this.calculatePercentage(overall.functions);
|
|
this.calculatePercentage(overall.branches);
|
|
this.calculatePercentage(overall.statements);
|
|
|
|
return overall;
|
|
}
|
|
|
|
private getPackageFromPath(filePath: string): string {
|
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
|
|
// Try to extract package name from path
|
|
const patterns = [
|
|
/packages\/([^/]+)\//,
|
|
/apps\/stock\/([^/]+)\//,
|
|
/apps\/([^/]+)\//,
|
|
/libs\/core\/([^/]+)\//,
|
|
/libs\/data\/([^/]+)\//,
|
|
/libs\/services\/([^/]+)\//,
|
|
/libs\/([^/]+)\//,
|
|
/tools\/([^/]+)\//,
|
|
/@stock-bot\/([^/]+)\//,
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = normalizedPath.match(pattern);
|
|
if (match) {
|
|
return `@stock-bot/${match[1]}`;
|
|
}
|
|
}
|
|
|
|
// Default to root
|
|
return 'root';
|
|
}
|
|
|
|
private getPackagePath(filePath: string): string {
|
|
const normalizedPath = filePath.replace(/\\/g, '/');
|
|
|
|
// Extract package root path
|
|
const patterns = [
|
|
/(.*\/packages\/[^/]+)\//,
|
|
/(.*\/apps\/stock\/[^/]+)\//,
|
|
/(.*\/apps\/[^/]+)\//,
|
|
/(.*\/libs\/core\/[^/]+)\//,
|
|
/(.*\/libs\/data\/[^/]+)\//,
|
|
/(.*\/libs\/services\/[^/]+)\//,
|
|
/(.*\/libs\/[^/]+)\//,
|
|
/(.*\/tools\/[^/]+)\//,
|
|
];
|
|
|
|
for (const pattern of patterns) {
|
|
const match = normalizedPath.match(pattern);
|
|
if (match) {
|
|
return match[1];
|
|
}
|
|
}
|
|
|
|
// Default to workspace root
|
|
return this.config.workspaceRoot || process.cwd();
|
|
}
|
|
|
|
checkThresholds(report: CoverageReport): {
|
|
passed: boolean;
|
|
failures: Array<{ metric: string; expected: number; actual: number }>;
|
|
} {
|
|
const failures: Array<{ metric: string; expected: number; actual: number }> = [];
|
|
const { thresholds } = this.config;
|
|
const { overall } = report;
|
|
|
|
if (thresholds.lines !== undefined && overall.lines.percentage < thresholds.lines) {
|
|
failures.push({
|
|
metric: 'lines',
|
|
expected: thresholds.lines,
|
|
actual: overall.lines.percentage,
|
|
});
|
|
}
|
|
|
|
if (thresholds.functions !== undefined && overall.functions.percentage < thresholds.functions) {
|
|
failures.push({
|
|
metric: 'functions',
|
|
expected: thresholds.functions,
|
|
actual: overall.functions.percentage,
|
|
});
|
|
}
|
|
|
|
if (thresholds.branches !== undefined && overall.branches.percentage < thresholds.branches) {
|
|
failures.push({
|
|
metric: 'branches',
|
|
expected: thresholds.branches,
|
|
actual: overall.branches.percentage,
|
|
});
|
|
}
|
|
|
|
if (thresholds.statements !== undefined && overall.statements.percentage < thresholds.statements) {
|
|
failures.push({
|
|
metric: 'statements',
|
|
expected: thresholds.statements,
|
|
actual: overall.statements.percentage,
|
|
});
|
|
}
|
|
|
|
return {
|
|
passed: failures.length === 0,
|
|
failures,
|
|
};
|
|
}
|
|
} |