use crate::core::{DebtItem, DebtType, FunctionMetrics, Priority};
use crate::effects::validation::ValidationRuleSet;
use std::path::PathBuf;
use stillwater::predicate::*;
#[derive(Debug, Clone)]
pub struct DebtViolation {
pub debt_type: DebtType,
pub priority: Priority,
pub message: String,
pub rule_name: String,
}
impl DebtViolation {
pub fn into_debt_item(self, file: PathBuf, line: usize) -> DebtItem {
DebtItem {
id: format!("{}:{}:{}", file.display(), line, self.rule_name),
debt_type: self.debt_type,
priority: self.priority,
file,
line,
column: None,
message: self.message,
context: Some(format!("Rule: {}", self.rule_name)),
}
}
}
#[derive(Debug, Clone, Default)]
pub struct FunctionDebtRules {
pub rules: ValidationRuleSet,
}
impl FunctionDebtRules {
pub fn new(rules: ValidationRuleSet) -> Self {
Self { rules }
}
pub fn strict() -> Self {
Self {
rules: ValidationRuleSet::strict(),
}
}
pub fn lenient() -> Self {
Self {
rules: ValidationRuleSet::lenient(),
}
}
pub fn check(&self, metrics: &FunctionMetrics) -> Vec<DebtViolation> {
let mut violations = Vec::new();
if let Some(violation) = self.check_cyclomatic_complexity(metrics) {
violations.push(violation);
}
if let Some(violation) = self.check_cognitive_complexity(metrics) {
violations.push(violation);
}
if let Some(violation) = self.check_function_length(metrics) {
violations.push(violation);
}
if let Some(violation) = self.check_nesting_depth(metrics) {
violations.push(violation);
}
violations
}
fn check_cyclomatic_complexity(&self, metrics: &FunctionMetrics) -> Option<DebtViolation> {
let complexity = metrics.cyclomatic;
let critical_pred = ge(self.rules.complexity_critical);
let warning_pred =
ge(self.rules.complexity_warning).and(lt(self.rules.complexity_critical));
if critical_pred.check(&complexity) {
Some(DebtViolation {
debt_type: DebtType::Complexity {
cyclomatic: complexity,
cognitive: metrics.cognitive,
},
priority: Priority::Critical,
message: format!(
"Function '{}' has critical cyclomatic complexity: {} (threshold: {})",
metrics.name, complexity, self.rules.complexity_critical
),
rule_name: "critical_cyclomatic_complexity".to_string(),
})
} else if warning_pred.check(&complexity) {
Some(DebtViolation {
debt_type: DebtType::Complexity {
cyclomatic: complexity,
cognitive: metrics.cognitive,
},
priority: Priority::High,
message: format!(
"Function '{}' has high cyclomatic complexity: {} (warning threshold: {})",
metrics.name, complexity, self.rules.complexity_warning
),
rule_name: "high_cyclomatic_complexity".to_string(),
})
} else {
None
}
}
fn check_cognitive_complexity(&self, metrics: &FunctionMetrics) -> Option<DebtViolation> {
let complexity = metrics.cognitive;
let critical_pred = ge(self.rules.complexity_critical);
let warning_pred =
ge(self.rules.complexity_warning).and(lt(self.rules.complexity_critical));
if critical_pred.check(&complexity) {
Some(DebtViolation {
debt_type: DebtType::Complexity {
cyclomatic: metrics.cyclomatic,
cognitive: complexity,
},
priority: Priority::Critical,
message: format!(
"Function '{}' has critical cognitive complexity: {} (threshold: {})",
metrics.name, complexity, self.rules.complexity_critical
),
rule_name: "critical_cognitive_complexity".to_string(),
})
} else if warning_pred.check(&complexity) {
Some(DebtViolation {
debt_type: DebtType::Complexity {
cyclomatic: metrics.cyclomatic,
cognitive: complexity,
},
priority: Priority::Medium,
message: format!(
"Function '{}' has high cognitive complexity: {} (warning threshold: {})",
metrics.name, complexity, self.rules.complexity_warning
),
rule_name: "high_cognitive_complexity".to_string(),
})
} else {
None
}
}
fn check_function_length(&self, metrics: &FunctionMetrics) -> Option<DebtViolation> {
let length = metrics.length;
let too_long = gt(self.rules.max_function_length);
if too_long.check(&length) {
let priority = if length > self.rules.max_function_length * 2 {
Priority::High
} else {
Priority::Medium
};
Some(DebtViolation {
debt_type: DebtType::CodeSmell {
smell_type: Some("long_function".to_string()),
},
priority,
message: format!(
"Function '{}' is too long: {} lines (max: {})",
metrics.name, length, self.rules.max_function_length
),
rule_name: "function_too_long".to_string(),
})
} else {
None
}
}
fn check_nesting_depth(&self, metrics: &FunctionMetrics) -> Option<DebtViolation> {
let nesting = metrics.nesting;
let too_deep = gt(self.rules.max_nesting_depth);
if too_deep.check(&nesting) {
let priority = if nesting > self.rules.max_nesting_depth * 2 {
Priority::High
} else {
Priority::Medium
};
Some(DebtViolation {
debt_type: DebtType::CodeSmell {
smell_type: Some("deep_nesting".to_string()),
},
priority,
message: format!(
"Function '{}' has excessive nesting: {} levels (max: {})",
metrics.name, nesting, self.rules.max_nesting_depth
),
rule_name: "excessive_nesting".to_string(),
})
} else {
None
}
}
}
pub struct NamingRules {
pub min_length: usize,
pub max_length: usize,
}
impl Default for NamingRules {
fn default() -> Self {
Self {
min_length: 2,
max_length: 50,
}
}
}
impl NamingRules {
pub fn check_function_name(&self, name: &str) -> Option<DebtViolation> {
if name.len() < self.min_length {
return Some(DebtViolation {
debt_type: DebtType::CodeSmell {
smell_type: Some("naming_convention".to_string()),
},
priority: Priority::Low,
message: format!(
"Function name '{}' is too short (min: {} chars)",
name, self.min_length
),
rule_name: "short_function_name".to_string(),
});
}
if name.len() > self.max_length {
return Some(DebtViolation {
debt_type: DebtType::CodeSmell {
smell_type: Some("naming_convention".to_string()),
},
priority: Priority::Low,
message: format!(
"Function name '{}' is too long (max: {} chars)",
name, self.max_length
),
rule_name: "long_function_name".to_string(),
});
}
let first_char = name.chars().next()?;
if first_char.is_uppercase() {
return Some(DebtViolation {
debt_type: DebtType::CodeSmell {
smell_type: Some("naming_convention".to_string()),
},
priority: Priority::Low,
message: format!(
"Function name '{}' should be snake_case (starts with uppercase)",
name
),
rule_name: "function_name_case".to_string(),
});
}
None
}
}
#[derive(Debug, Clone)]
pub struct DebtSeverityRules {
pub high_complexity_threshold: u32,
pub critical_complexity_threshold: u32,
pub high_length_threshold: usize,
pub critical_length_threshold: usize,
}
impl Default for DebtSeverityRules {
fn default() -> Self {
Self {
high_complexity_threshold: 21,
critical_complexity_threshold: 50,
high_length_threshold: 50,
critical_length_threshold: 100,
}
}
}
impl DebtSeverityRules {
pub fn complexity_severity(&self, complexity: u32) -> Option<Priority> {
let critical = ge(self.critical_complexity_threshold);
let high = ge(self.high_complexity_threshold).and(lt(self.critical_complexity_threshold));
if critical.check(&complexity) {
Some(Priority::Critical)
} else if high.check(&complexity) {
Some(Priority::High)
} else {
None
}
}
pub fn length_severity(&self, length: usize) -> Option<Priority> {
let critical = ge(self.critical_length_threshold);
let high = ge(self.high_length_threshold).and(lt(self.critical_length_threshold));
if critical.check(&length) {
Some(Priority::Critical)
} else if high.check(&length) {
Some(Priority::High)
} else {
None
}
}
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_function(
name: &str,
cyclomatic: u32,
cognitive: u32,
length: usize,
nesting: u32,
) -> FunctionMetrics {
FunctionMetrics {
name: name.to_string(),
file: PathBuf::from("test.rs"),
line: 1,
cyclomatic,
cognitive,
nesting,
length,
is_test: false,
visibility: None,
is_trait_method: false,
in_test_module: false,
entropy_score: None,
is_pure: None,
purity_confidence: None,
purity_reason: None,
call_dependencies: None,
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
}
}
#[test]
fn test_function_debt_rules_no_violations() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("simple_fn", 5, 3, 20, 2);
let violations = rules.check(&metrics);
assert!(violations.is_empty());
}
#[test]
fn test_function_debt_rules_critical_complexity() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("complex_fn", 150, 5, 20, 2);
let violations = rules.check(&metrics);
assert!(!violations.is_empty());
let critical = violations.iter().find(|v| v.priority == Priority::Critical);
assert!(critical.is_some());
assert!(critical
.unwrap()
.message
.contains("critical cyclomatic complexity"));
}
#[test]
fn test_function_debt_rules_warning_complexity() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("moderately_complex", 50, 5, 20, 2);
let violations = rules.check(&metrics);
assert!(!violations.is_empty());
let high = violations.iter().find(|v| v.priority == Priority::High);
assert!(high.is_some());
assert!(high.unwrap().message.contains("high cyclomatic complexity"));
}
#[test]
fn test_function_debt_rules_long_function() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("long_fn", 5, 3, 100, 2);
let violations = rules.check(&metrics);
assert!(!violations.is_empty());
let length_violation = violations
.iter()
.find(|v| v.rule_name == "function_too_long");
assert!(length_violation.is_some());
}
#[test]
fn test_function_debt_rules_deep_nesting() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("deep_fn", 5, 3, 20, 10);
let violations = rules.check(&metrics);
assert!(!violations.is_empty());
let nesting_violation = violations
.iter()
.find(|v| v.rule_name == "excessive_nesting");
assert!(nesting_violation.is_some());
}
#[test]
fn test_function_debt_rules_multiple_violations() {
let rules = FunctionDebtRules::default();
let metrics = create_test_function("problematic_fn", 150, 150, 200, 10);
let violations = rules.check(&metrics);
assert!(violations.len() >= 4);
}
#[test]
fn test_function_debt_rules_strict() {
let rules = FunctionDebtRules::strict();
let metrics = create_test_function("medium_fn", 15, 12, 30, 3);
let violations = rules.check(&metrics);
assert!(!violations.is_empty());
}
#[test]
fn test_function_debt_rules_lenient() {
let rules = FunctionDebtRules::lenient();
let metrics = create_test_function("larger_fn", 25, 20, 80, 5);
let violations = rules.check(&metrics);
assert!(violations.is_empty());
}
#[test]
fn test_debt_violation_into_debt_item() {
let violation = DebtViolation {
debt_type: DebtType::Complexity {
cyclomatic: 50,
cognitive: 30,
},
priority: Priority::High,
message: "Test violation".to_string(),
rule_name: "test_rule".to_string(),
};
let debt_item = violation.into_debt_item(PathBuf::from("src/test.rs"), 42);
assert_eq!(debt_item.line, 42);
assert!(debt_item.id.contains("src/test.rs"));
assert_eq!(debt_item.priority, Priority::High);
assert!(debt_item.context.as_ref().unwrap().contains("test_rule"));
}
#[test]
fn test_naming_rules_valid_name() {
let rules = NamingRules::default();
assert!(rules.check_function_name("valid_function_name").is_none());
}
#[test]
fn test_naming_rules_too_short() {
let rules = NamingRules::default();
let violation = rules.check_function_name("x");
assert!(violation.is_some());
assert!(violation.unwrap().message.contains("too short"));
}
#[test]
fn test_naming_rules_too_long() {
let rules = NamingRules {
min_length: 2,
max_length: 10,
};
let violation = rules.check_function_name("very_long_function_name");
assert!(violation.is_some());
assert!(violation.unwrap().message.contains("too long"));
}
#[test]
fn test_naming_rules_uppercase_start() {
let rules = NamingRules::default();
let violation = rules.check_function_name("UpperCaseStart");
assert!(violation.is_some());
assert!(violation.unwrap().message.contains("snake_case"));
}
#[test]
fn test_debt_severity_rules_complexity() {
let rules = DebtSeverityRules::default();
assert_eq!(rules.complexity_severity(10), None);
assert_eq!(rules.complexity_severity(30), Some(Priority::High));
assert_eq!(rules.complexity_severity(100), Some(Priority::Critical));
}
#[test]
fn test_debt_severity_rules_length() {
let rules = DebtSeverityRules::default();
assert_eq!(rules.length_severity(20), None);
assert_eq!(rules.length_severity(75), Some(Priority::High));
assert_eq!(rules.length_severity(150), Some(Priority::Critical));
}
}