#![cfg_attr(coverage_nightly, coverage(off))]
use serde::{Deserialize, Serialize};
use super::TdgScore;
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FunctionComplexity {
pub name: String,
pub line_number: usize,
pub cyclomatic: u32,
pub cognitive: u32,
pub tdg_impact: f64,
pub severity: ComplexitySeverity,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
pub enum ComplexitySeverity {
Low,
Medium,
High,
Critical,
}
impl ComplexitySeverity {
pub fn from_cyclomatic(complexity: u32) -> Self {
match complexity {
0..=5 => Self::Low,
6..=10 => Self::Medium,
11..=20 => Self::High,
_ => Self::Critical,
}
}
}
impl std::fmt::Display for ComplexitySeverity {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::Low => write!(f, "Low"),
Self::Medium => write!(f, "Medium"),
Self::High => write!(f, "High"),
Self::Critical => write!(f, "Critical"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ActionableRecommendation {
pub rec_type: RecommendationType,
pub action: String,
pub lines: Vec<usize>,
pub expected_impact: f64,
pub estimated_hours: f64,
pub priority: u8,
pub pattern: String,
}
#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
pub enum RecommendationType {
ExtractMacro,
SplitModule,
ReduceComplexity,
ExtractFunction,
ReplaceConditional,
}
impl std::fmt::Display for RecommendationType {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
Self::ExtractMacro => write!(f, "extract_macro"),
Self::SplitModule => write!(f, "split_module"),
Self::ReduceComplexity => write!(f, "reduce_complexity"),
Self::ExtractFunction => write!(f, "extract_function"),
Self::ReplaceConditional => write!(f, "replace_conditional"),
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ExplainedTDGScore {
pub score: TdgScore,
pub functions: Vec<FunctionComplexity>,
pub recommendations: Vec<ActionableRecommendation>,
pub baseline_comparison: Option<ExplainBaselineComparison>,
}
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct ExplainBaselineComparison {
pub baseline_ref: String,
pub baseline_score: f32,
pub current_score: f32,
pub delta: f32,
pub completed: Vec<String>,
pub pending: Vec<String>,
}
impl ExplainedTDGScore {
pub fn new(score: TdgScore) -> Self {
Self {
score,
functions: Vec::new(),
recommendations: Vec::new(),
baseline_comparison: None,
}
}
pub fn add_function(&mut self, func: FunctionComplexity) {
self.functions.push(func);
}
pub fn add_recommendation(&mut self, rec: ActionableRecommendation) {
self.recommendations.push(rec);
}
pub fn sort_functions_by_impact(&mut self) {
self.functions.sort_by(|a, b| {
b.tdg_impact
.partial_cmp(&a.tdg_impact)
.unwrap_or(std::cmp::Ordering::Equal)
});
}
pub fn sort_recommendations(&mut self) {
self.recommendations
.sort_by(|a, b| match a.priority.cmp(&b.priority) {
std::cmp::Ordering::Equal => b
.expected_impact
.partial_cmp(&a.expected_impact)
.unwrap_or(std::cmp::Ordering::Equal),
other => other,
});
}
pub fn filter_functions_by_threshold(&mut self, threshold: u32) {
self.functions.retain(|f| f.cyclomatic >= threshold);
}
pub fn total_functions(&self) -> usize {
self.functions.len()
}
pub fn total_recommendations(&self) -> usize {
self.recommendations.len()
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_complexity_severity_classification() {
assert_eq!(
ComplexitySeverity::from_cyclomatic(3),
ComplexitySeverity::Low
);
assert_eq!(
ComplexitySeverity::from_cyclomatic(7),
ComplexitySeverity::Medium
);
assert_eq!(
ComplexitySeverity::from_cyclomatic(15),
ComplexitySeverity::High
);
assert_eq!(
ComplexitySeverity::from_cyclomatic(25),
ComplexitySeverity::Critical
);
}
#[test]
fn test_explained_score_function_sorting() {
let mut explained = ExplainedTDGScore::new(TdgScore::default());
explained.add_function(FunctionComplexity {
name: "low_impact".to_string(),
line_number: 10,
cyclomatic: 5,
cognitive: 6,
tdg_impact: 1.0,
severity: ComplexitySeverity::Low,
});
explained.add_function(FunctionComplexity {
name: "high_impact".to_string(),
line_number: 50,
cyclomatic: 20,
cognitive: 25,
tdg_impact: 4.5,
severity: ComplexitySeverity::Critical,
});
explained.add_function(FunctionComplexity {
name: "medium_impact".to_string(),
line_number: 30,
cyclomatic: 12,
cognitive: 15,
tdg_impact: 2.5,
severity: ComplexitySeverity::High,
});
explained.sort_functions_by_impact();
assert_eq!(explained.functions[0].name, "high_impact");
assert_eq!(explained.functions[1].name, "medium_impact");
assert_eq!(explained.functions[2].name, "low_impact");
}
#[test]
fn test_explained_score_threshold_filtering() {
let mut explained = ExplainedTDGScore::new(TdgScore::default());
explained.add_function(FunctionComplexity {
name: "simple".to_string(),
line_number: 10,
cyclomatic: 5,
cognitive: 6,
tdg_impact: 1.0,
severity: ComplexitySeverity::Low,
});
explained.add_function(FunctionComplexity {
name: "complex".to_string(),
line_number: 50,
cyclomatic: 20,
cognitive: 25,
tdg_impact: 4.5,
severity: ComplexitySeverity::Critical,
});
explained.filter_functions_by_threshold(10);
assert_eq!(explained.total_functions(), 1);
assert_eq!(explained.functions[0].name, "complex");
}
#[test]
fn test_recommendation_sorting() {
let mut explained = ExplainedTDGScore::new(TdgScore::default());
explained.add_recommendation(ActionableRecommendation {
rec_type: RecommendationType::ExtractMacro,
action: "Low priority, low impact".to_string(),
lines: vec![10],
expected_impact: 2.0,
estimated_hours: 3.0,
priority: 3,
pattern: "test".to_string(),
});
explained.add_recommendation(ActionableRecommendation {
rec_type: RecommendationType::SplitModule,
action: "High priority, high impact".to_string(),
lines: vec![100],
expected_impact: 8.5,
estimated_hours: 5.0,
priority: 1,
pattern: "test".to_string(),
});
explained.add_recommendation(ActionableRecommendation {
rec_type: RecommendationType::ReduceComplexity,
action: "High priority, medium impact".to_string(),
lines: vec![50],
expected_impact: 5.0,
estimated_hours: 4.0,
priority: 1,
pattern: "test".to_string(),
});
explained.sort_recommendations();
assert_eq!(explained.recommendations[0].expected_impact, 8.5);
assert_eq!(explained.recommendations[0].priority, 1);
assert_eq!(explained.recommendations[1].expected_impact, 5.0);
assert_eq!(explained.recommendations[1].priority, 1);
assert_eq!(explained.recommendations[2].expected_impact, 2.0);
assert_eq!(explained.recommendations[2].priority, 3);
}
}