use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityProfile {
pub name: String,
pub thresholds: QualityThresholds,
pub patterns: DesignPatterns,
pub rules: Vec<QualityRule>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityThresholds {
pub max_complexity: u32,
pub max_cognitive: u32,
pub min_coverage: u32,
pub max_tdg: u32,
pub max_entropy_violations: u32, pub zero_satd: bool,
pub zero_dead_code: bool,
pub require_doctests: bool,
pub require_property_tests: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DesignPatterns {
pub enforce_solid: bool,
pub enforce_dry: bool,
pub enforce_kiss: bool,
pub enforce_yagni: bool,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct QualityRule {
pub name: String,
pub description: String,
pub severity: Severity,
pub pattern: String,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum Severity {
Error,
Warning,
Info,
}
impl QualityProfile {
#[must_use]
pub fn extreme() -> Self {
Self {
name: "extreme".to_string(),
thresholds: QualityThresholds {
max_complexity: 5,
max_cognitive: 5,
min_coverage: 90,
max_tdg: 3,
max_entropy_violations: 2, zero_satd: true,
zero_dead_code: true,
require_doctests: true,
require_property_tests: true,
},
patterns: DesignPatterns {
enforce_solid: true,
enforce_dry: true,
enforce_kiss: true,
enforce_yagni: true,
},
rules: Self::default_rules(),
}
}
#[must_use]
pub fn standard() -> Self {
Self {
name: "standard".to_string(),
thresholds: QualityThresholds {
max_complexity: 10,
max_cognitive: 10,
min_coverage: 80,
max_tdg: 5,
max_entropy_violations: 5, zero_satd: true,
zero_dead_code: false,
require_doctests: true,
require_property_tests: false,
},
patterns: DesignPatterns {
enforce_solid: true,
enforce_dry: true,
enforce_kiss: true,
enforce_yagni: false,
},
rules: Self::default_rules(),
}
}
#[must_use]
pub fn relaxed() -> Self {
Self {
name: "relaxed".to_string(),
thresholds: QualityThresholds {
max_complexity: 20,
max_cognitive: 20,
min_coverage: 60,
max_tdg: 10,
max_entropy_violations: 15, zero_satd: false,
zero_dead_code: false,
require_doctests: false,
require_property_tests: false,
},
patterns: DesignPatterns {
enforce_solid: false,
enforce_dry: false,
enforce_kiss: true,
enforce_yagni: false,
},
rules: Self::minimal_rules(),
}
}
fn default_rules() -> Vec<QualityRule> {
vec![
QualityRule {
name: "no_panic".to_string(),
description: "Functions should not panic".to_string(),
severity: Severity::Error,
pattern: r"panic!|unwrap|expect".to_string(),
},
QualityRule {
name: "proper_error_handling".to_string(),
description: "Use Result types for error handling".to_string(),
severity: Severity::Warning,
pattern: r"Option.*unwrap".to_string(),
},
]
}
fn minimal_rules() -> Vec<QualityRule> {
vec![QualityRule {
name: "no_panic".to_string(),
description: "Functions should not panic".to_string(),
severity: Severity::Warning,
pattern: r"panic!".to_string(),
}]
}
#[must_use]
pub fn meets_thresholds(&self, metrics: &QualityMetrics) -> bool {
metrics.complexity <= self.thresholds.max_complexity
&& metrics.cognitive_complexity <= self.thresholds.max_cognitive
&& metrics.coverage >= self.thresholds.min_coverage
&& metrics.tdg <= self.thresholds.max_tdg
}
}
#[derive(Debug, Clone, Default)]
pub struct QualityMetrics {
pub complexity: u32,
pub cognitive_complexity: u32,
pub coverage: u32,
pub tdg: u32,
pub satd_count: u32,
pub dead_code_percentage: u32,
pub has_doctests: bool,
pub has_property_tests: bool,
}
impl QualityMetrics {
#[must_use]
pub fn calculate_score(&self) -> f64 {
let complexity_score = (f64::from(20_u32.saturating_sub(self.complexity)) / 20.0) * 25.0;
let coverage_score = (f64::from(self.coverage) / 100.0) * 25.0;
let tdg_score = (f64::from(10_u32.saturating_sub(self.tdg)) / 10.0) * 25.0;
let defect_score = if self.satd_count == 0 { 25.0 } else { 0.0 };
complexity_score + coverage_score + tdg_score + defect_score
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_quality_profile_extreme() {
let profile = QualityProfile::extreme();
assert_eq!(profile.name, "extreme");
assert_eq!(profile.thresholds.max_complexity, 5);
assert_eq!(profile.thresholds.min_coverage, 90);
assert!(profile.thresholds.zero_satd);
}
#[test]
fn test_quality_profile_standard() {
let profile = QualityProfile::standard();
assert_eq!(profile.name, "standard");
assert_eq!(profile.thresholds.max_complexity, 10);
assert_eq!(profile.thresholds.min_coverage, 80);
assert!(profile.thresholds.zero_satd);
}
#[test]
fn test_quality_profile_relaxed() {
let profile = QualityProfile::relaxed();
assert_eq!(profile.name, "relaxed");
assert_eq!(profile.thresholds.max_complexity, 20);
assert_eq!(profile.thresholds.min_coverage, 60);
assert!(!profile.thresholds.zero_satd);
}
#[test]
fn test_quality_metrics_default() {
let metrics = QualityMetrics::default();
assert_eq!(metrics.complexity, 0);
assert_eq!(metrics.coverage, 0);
assert_eq!(metrics.tdg, 0);
assert!(!metrics.has_doctests);
}
#[test]
fn test_quality_metrics_score_perfect() {
let metrics = QualityMetrics {
complexity: 1,
cognitive_complexity: 1,
coverage: 100,
tdg: 1,
satd_count: 0,
dead_code_percentage: 0,
has_doctests: true,
has_property_tests: true,
};
let score = metrics.calculate_score();
assert!(score >= 95.0); }
#[test]
fn test_quality_metrics_score_poor() {
let metrics = QualityMetrics {
complexity: 30,
cognitive_complexity: 30,
coverage: 10,
tdg: 15,
satd_count: 5,
dead_code_percentage: 50,
has_doctests: false,
has_property_tests: false,
};
let score = metrics.calculate_score();
assert!(score <= 20.0); }
#[test]
fn test_meets_thresholds_success() {
let profile = QualityProfile::standard();
let metrics = QualityMetrics {
complexity: 8,
cognitive_complexity: 8,
coverage: 85,
tdg: 4,
satd_count: 0,
dead_code_percentage: 0,
has_doctests: true,
has_property_tests: false,
};
assert!(profile.meets_thresholds(&metrics));
}
#[test]
fn test_meets_thresholds_failure() {
let profile = QualityProfile::extreme();
let metrics = QualityMetrics {
complexity: 15, cognitive_complexity: 5,
coverage: 95,
tdg: 2,
satd_count: 0,
dead_code_percentage: 0,
has_doctests: true,
has_property_tests: true,
};
assert!(!profile.meets_thresholds(&metrics));
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod property_tests {
use proptest::prelude::*;
proptest! {
#[test]
fn basic_property_stability(_input in ".*") {
prop_assert!(true);
}
#[test]
fn module_consistency_check(_x in 0u32..1000) {
prop_assert!(_x < 1001);
}
}
}