UNPKG

deepsource-mcp-server

Version:
207 lines (181 loc) 5.69 kB
/** * @fileoverview CoveragePercentage value object * * This module defines the CoveragePercentage value object which represents * code coverage as a percentage with specific validation and formatting rules. */ import { ValueObject } from '../shared/value-object.js'; /** * Properties for the CoveragePercentage value object */ interface CoveragePercentageProps { value: number; decimalPlaces: number; } /** * Value object representing code coverage as a percentage * * Ensures that coverage values are always between 0 and 100 (inclusive) * and provides specialized formatting and comparison methods. * * @example * ```typescript * const coverage = CoveragePercentage.create(85.567); * console.log(coverage.toString()); // "85.6%" * console.log(coverage.isAcceptable(80)); // true * * const perfect = CoveragePercentage.perfect(); * console.log(perfect.value); // 100 * ``` */ export class CoveragePercentage extends ValueObject<CoveragePercentageProps> { // Private constructor enforces factory pattern - instances must be created via static methods // skipcq: JS-0358 - This constructor is necessary to prevent direct instantiation private constructor(props: CoveragePercentageProps) { super(props); } /** * Creates a new CoveragePercentage instance * * @param value - The coverage percentage (0-100) * @param decimalPlaces - Number of decimal places for display (default: 1) * @returns A new CoveragePercentage instance * @throws Error if value is outside 0-100 range */ static create(value: number, decimalPlaces = 1): CoveragePercentage { if (!Number.isFinite(value)) { throw new Error('Coverage percentage must be a finite number'); } if (value < 0 || value > 100) { throw new Error('Coverage percentage must be between 0 and 100'); } if (!Number.isInteger(decimalPlaces) || decimalPlaces < 0) { throw new Error('Decimal places must be a non-negative integer'); } return new CoveragePercentage({ value, decimalPlaces, }); } /** * Creates a zero coverage instance * * @returns A new CoveragePercentage with 0% coverage */ static zero(): CoveragePercentage { return CoveragePercentage.create(0); } /** * Creates a perfect coverage instance * * @returns A new CoveragePercentage with 100% coverage */ static perfect(): CoveragePercentage { return CoveragePercentage.create(100); } /** * Creates a coverage percentage from a fraction * * @param covered - Number of covered items * @param total - Total number of items * @param decimalPlaces - Number of decimal places for display * @returns A new CoveragePercentage instance * @throws Error if total is zero or negative */ static fromFraction(covered: number, total: number, decimalPlaces = 1): CoveragePercentage { if (total <= 0) { throw new Error('Total must be positive'); } if (covered < 0) { throw new Error('Covered count cannot be negative'); } if (covered > total) { throw new Error('Covered count cannot exceed total'); } const percentage = (covered / total) * 100; return CoveragePercentage.create(percentage, decimalPlaces); } /** * Gets the percentage value */ get value(): number { return this.props.value; } /** * Gets the number of decimal places for display */ get decimalPlaces(): number { return this.props.decimalPlaces; } /** * Checks if coverage is zero */ get isZero(): boolean { return this.props.value === 0; } /** * Checks if coverage is perfect (100%) */ get isPerfect(): boolean { return this.props.value === 100; } /** * Gets the coverage level category */ get level(): 'excellent' | 'good' | 'fair' | 'poor' { if (this.props.value >= 90) return 'excellent'; if (this.props.value >= 80) return 'good'; if (this.props.value >= 60) return 'fair'; return 'poor'; } /** * Checks if the coverage meets or exceeds a threshold * * @param threshold - The minimum acceptable percentage * @returns True if coverage meets the threshold */ isAcceptable(threshold: number): boolean { return this.props.value >= threshold; } /** * Calculates the improvement needed to reach a target * * @param target - The target percentage * @returns The percentage points needed to reach the target */ improvementNeeded(target: number): number { return Math.max(0, target - this.props.value); } /** * Combines this coverage with another (weighted average) * * @param other - The other coverage percentage * @param thisWeight - Weight for this coverage (default: 1) * @param otherWeight - Weight for the other coverage (default: 1) * @returns A new CoveragePercentage with the weighted average */ combine(other: CoveragePercentage, thisWeight = 1, otherWeight = 1): CoveragePercentage { const totalWeight = thisWeight + otherWeight; if (totalWeight === 0) { throw new Error('Total weight cannot be zero'); } const weightedAverage = (this.props.value * thisWeight + other.props.value * otherWeight) / totalWeight; return CoveragePercentage.create( weightedAverage, Math.max(this.props.decimalPlaces, other.props.decimalPlaces) ); } /** * Returns a formatted string representation */ override toString(): string { return `${this.props.value.toFixed(this.props.decimalPlaces)}%`; } /** * Returns a display string with the coverage level */ toDisplayString(): string { return `${this.toString()} (${this.level})`; } }