deepsource-mcp-server
Version:
Model Context Protocol server for DeepSource
207 lines (181 loc) • 5.69 kB
text/typescript
/**
* @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})`;
}
}