use chrono::{DateTime, Utc};
use serde::{Deserialize, Serialize};
use std::collections::{BTreeMap, HashMap};
use std::path::PathBuf;
pub type DefectId = String;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefectReport {
pub metadata: ReportMetadata,
pub defects: Vec<Defect>,
pub summary: DefectSummary,
pub file_index: BTreeMap<PathBuf, Vec<DefectId>>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct ReportMetadata {
pub tool: String,
pub version: String,
pub generated_at: DateTime<Utc>,
pub project_root: PathBuf,
pub total_files_analyzed: usize,
pub analysis_duration_ms: u64,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Defect {
pub id: DefectId,
pub severity: Severity,
pub category: DefectCategory,
pub file_path: PathBuf,
pub line_start: u32,
pub line_end: Option<u32>,
pub column_start: Option<u32>,
pub column_end: Option<u32>,
pub message: String,
pub rule_id: String,
pub fix_suggestion: Option<String>,
pub metrics: HashMap<String, f64>,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Low,
Medium,
High,
Critical,
}
impl std::fmt::Display for Severity {
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, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Serialize, Deserialize)]
#[serde(rename_all = "snake_case")]
pub enum DefectCategory {
Complexity,
TechnicalDebt,
DeadCode,
Duplication,
Performance,
Architecture,
TestCoverage,
}
impl DefectCategory {
#[must_use]
pub fn all() -> Vec<Self> {
vec![
Self::Complexity,
Self::TechnicalDebt,
Self::DeadCode,
Self::Duplication,
Self::Performance,
Self::Architecture,
Self::TestCoverage,
]
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DefectSummary {
pub total_defects: usize,
pub by_severity: BTreeMap<String, usize>,
pub by_category: BTreeMap<String, usize>,
pub hotspot_files: Vec<FileHotspot>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct FileHotspot {
pub path: PathBuf,
pub defect_count: usize,
pub severity_score: f64,
}
impl Defect {
#[must_use]
pub fn generate_id(prefix: &str, index: usize) -> DefectId {
format!("{}-{:03}", prefix, index + 1)
}
#[must_use]
pub fn severity_weight(&self) -> f64 {
match self.severity {
Severity::Critical => 10.0,
Severity::High => 5.0,
Severity::Medium => 3.0,
Severity::Low => 1.0,
}
}
}
impl std::fmt::Display for DefectCategory {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
match self {
DefectCategory::Complexity => write!(f, "Complexity"),
DefectCategory::TechnicalDebt => write!(f, "Technical Debt"),
DefectCategory::DeadCode => write!(f, "Dead Code"),
DefectCategory::Duplication => write!(f, "Duplication"),
DefectCategory::Performance => write!(f, "Performance"),
DefectCategory::Architecture => write!(f, "Architecture"),
DefectCategory::TestCoverage => write!(f, "Test Coverage"),
}
}
}
#[derive(Debug, Clone)]
pub struct FileRankingConfig {
pub use_severity: bool,
pub use_count: bool,
pub category_weights: HashMap<DefectCategory, f64>,
}
impl Default for FileRankingConfig {
fn default() -> Self {
let mut category_weights = HashMap::new();
category_weights.insert(DefectCategory::Complexity, 1.5);
category_weights.insert(DefectCategory::Performance, 2.0);
category_weights.insert(DefectCategory::Architecture, 1.8);
category_weights.insert(DefectCategory::TechnicalDebt, 1.2);
category_weights.insert(DefectCategory::DeadCode, 1.0);
category_weights.insert(DefectCategory::Duplication, 1.3);
category_weights.insert(DefectCategory::TestCoverage, 0.8);
Self {
use_severity: true,
use_count: true,
category_weights,
}
}
}
#[derive(Debug, Clone)]
pub struct RankedFile {
pub rank: usize,
pub score: f64,
pub path: PathBuf,
pub defects: Vec<Defect>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_defect_id_generation() {
assert_eq!(Defect::generate_id("CPLX", 0), "CPLX-001");
assert_eq!(Defect::generate_id("SATD", 99), "SATD-100");
}
#[test]
fn test_severity_ordering() {
assert!(Severity::Critical > Severity::High);
assert!(Severity::High > Severity::Medium);
assert!(Severity::Medium > Severity::Low);
}
#[test]
fn test_severity_weight() {
let defect = Defect {
id: "TEST-001".to_string(),
severity: Severity::Critical,
category: DefectCategory::Complexity,
file_path: PathBuf::from("test.rs"),
line_start: 1,
line_end: None,
column_start: None,
column_end: None,
message: "Test".to_string(),
rule_id: "test".to_string(),
fix_suggestion: None,
metrics: HashMap::new(),
};
assert_eq!(defect.severity_weight(), 10.0);
}
}
#[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);
}
}
}