pub const SEVERITY_HIGH_THRESHOLD: usize = 5;
pub const SEVERITY_MEDIUM_THRESHOLD: usize = 2;
pub const PERCENTAGE_MULTIPLIER: f64 = 100.0;
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Classification {
Integration,
Operation,
Violation {
has_logic: bool,
has_own_calls: bool,
logic_locations: Vec<LogicOccurrence>,
call_locations: Vec<CallOccurrence>,
},
Trivial,
}
#[derive(Debug, Clone, PartialEq, Eq, serde::Serialize)]
#[serde(rename_all = "lowercase")]
pub enum Severity {
Low,
Medium,
High,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, derive_more::Display)]
#[display("{construct} at nesting {nesting_depth} (line {line})")]
pub struct ComplexityHotspot {
pub line: usize,
pub nesting_depth: usize,
pub construct: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq, derive_more::Display)]
#[display("{value} (line {line})")]
pub struct MagicNumberOccurrence {
pub line: usize,
pub value: String,
}
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct ComplexityMetrics {
pub logic_count: usize,
pub call_count: usize,
pub max_nesting: usize,
pub cognitive_complexity: usize,
pub cyclomatic_complexity: usize,
pub hotspots: Vec<ComplexityHotspot>,
pub magic_numbers: Vec<MagicNumberOccurrence>,
pub function_lines: usize,
pub unsafe_blocks: usize,
pub unwrap_count: usize,
pub expect_count: usize,
pub panic_count: usize,
pub todo_count: usize,
pub logic_occurrences: Vec<LogicOccurrence>,
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[display("{kind} (line {line})")]
pub struct LogicOccurrence {
pub kind: String, pub line: usize,
}
#[derive(Debug, Clone, PartialEq, Eq, derive_more::Display)]
#[display("{name} (line {line})")]
pub struct CallOccurrence {
pub name: String,
pub line: usize,
}
pub fn compute_severity(classification: &Classification) -> Option<Severity> {
if let Classification::Violation {
logic_locations,
call_locations,
..
} = classification
{
let total = logic_locations.len() + call_locations.len();
if total > SEVERITY_HIGH_THRESHOLD {
Some(Severity::High)
} else if total > SEVERITY_MEDIUM_THRESHOLD {
Some(Severity::Medium)
} else {
Some(Severity::Low)
}
} else {
None
}
}
pub const EFFORT_LOGIC_WEIGHT: f64 = 1.0;
pub const EFFORT_CALL_WEIGHT: f64 = 1.5;
pub const EFFORT_NESTING_WEIGHT: f64 = 2.0;
#[derive(Debug, Clone)]
pub struct FunctionAnalysis {
pub name: String,
pub file: String,
pub line: usize,
pub classification: Classification,
pub parent_type: Option<String>,
pub suppressed: bool,
pub complexity: Option<ComplexityMetrics>,
pub qualified_name: String,
pub severity: Option<Severity>,
pub cognitive_warning: bool,
pub cyclomatic_warning: bool,
pub nesting_depth_warning: bool,
pub function_length_warning: bool,
pub unsafe_warning: bool,
pub error_handling_warning: bool,
pub complexity_suppressed: bool,
pub own_calls: Vec<String>,
pub parameter_count: usize,
pub is_trait_impl: bool,
pub is_test: bool,
pub effort_score: Option<f64>,
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_logic_occurrence_display() {
let lo = LogicOccurrence {
kind: "if".to_string(),
line: 42,
};
assert_eq!(lo.to_string(), "if (line 42)");
}
#[test]
fn test_call_occurrence_display() {
let co = CallOccurrence {
name: "helper".to_string(),
line: 10,
};
assert_eq!(co.to_string(), "helper (line 10)");
}
#[test]
fn test_complexity_hotspot_display() {
let h = ComplexityHotspot {
line: 15,
nesting_depth: 3,
construct: "if".to_string(),
};
assert_eq!(h.to_string(), "if at nesting 3 (line 15)");
}
#[test]
fn test_magic_number_occurrence_display() {
let m = MagicNumberOccurrence {
line: 7,
value: "42".to_string(),
};
assert_eq!(m.to_string(), "42 (line 7)");
}
#[test]
fn test_complexity_metrics_default() {
let m = ComplexityMetrics::default();
assert_eq!(m.logic_count, 0);
assert_eq!(m.call_count, 0);
assert_eq!(m.max_nesting, 0);
assert_eq!(m.cognitive_complexity, 0);
assert_eq!(m.cyclomatic_complexity, 0);
assert!(m.hotspots.is_empty());
assert!(m.magic_numbers.is_empty());
}
#[test]
fn test_compute_severity_low() {
let c = Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![LogicOccurrence {
kind: "if".into(),
line: 1,
}],
call_locations: vec![CallOccurrence {
name: "f".into(),
line: 2,
}],
};
assert_eq!(compute_severity(&c), Some(Severity::Low));
}
#[test]
fn test_compute_severity_medium() {
let c = Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![
LogicOccurrence {
kind: "if".into(),
line: 1,
},
LogicOccurrence {
kind: "match".into(),
line: 2,
},
],
call_locations: vec![CallOccurrence {
name: "f".into(),
line: 3,
}],
};
assert_eq!(compute_severity(&c), Some(Severity::Medium));
}
#[test]
fn test_compute_severity_high() {
let c = Classification::Violation {
has_logic: true,
has_own_calls: true,
logic_locations: vec![
LogicOccurrence {
kind: "if".into(),
line: 1,
},
LogicOccurrence {
kind: "match".into(),
line: 2,
},
LogicOccurrence {
kind: "for".into(),
line: 3,
},
],
call_locations: vec![
CallOccurrence {
name: "a".into(),
line: 4,
},
CallOccurrence {
name: "b".into(),
line: 5,
},
CallOccurrence {
name: "c".into(),
line: 6,
},
],
};
assert_eq!(compute_severity(&c), Some(Severity::High));
}
#[test]
fn test_compute_severity_none_for_non_violation() {
assert_eq!(compute_severity(&Classification::Integration), None);
assert_eq!(compute_severity(&Classification::Operation), None);
assert_eq!(compute_severity(&Classification::Trivial), None);
}
}