use regex::RegexSet;
use super::types::{AstContext, AstNodeType, DebtCategory, DebtClassifier, DebtPattern, Severity};
impl Default for DebtClassifier {
fn default() -> Self {
Self::new()
}
}
impl DebtClassifier {
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new() -> Self {
let patterns = vec![
DebtPattern {
regex: r"(?i)\b(hack|kludge|smell|xxx)\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Architectural compromise".to_string(),
},
DebtPattern {
regex: r"(?i)\b(fixme|broken|bug)\b".to_string(),
category: DebtCategory::Defect,
severity: Severity::High,
description: "Known defect".to_string(),
},
DebtPattern {
regex: r"(?i)\btodo\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Low,
description: "Missing feature".to_string(),
},
DebtPattern {
regex: r"(?i)\b(security|vuln|vulnerability|cve|xss)\b".to_string(),
category: DebtCategory::Security,
severity: Severity::Critical,
description: "Security concern".to_string(),
},
DebtPattern {
regex: r"(?i)\bperformance\s+(issue|problem)\b".to_string(),
category: DebtCategory::Performance,
severity: Severity::Medium,
description: "Performance issue".to_string(),
},
DebtPattern {
regex: r"(?i)\btest.*\b(disabled|skipped|failing)\b".to_string(),
category: DebtCategory::Test,
severity: Severity::Medium,
description: "Test debt".to_string(),
},
DebtPattern {
regex: r"(?i)\btechnical\s+debt\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Explicit technical debt".to_string(),
},
DebtPattern {
regex: r"(?i)\bcode\s+smell\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Code smell".to_string(),
},
DebtPattern {
regex: r"(?i)\b(workaround|temp|temporary)\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Low,
description: "Temporary solution".to_string(),
},
DebtPattern {
regex: r"(?i)\b(optimize|slow)\b".to_string(),
category: DebtCategory::Performance,
severity: Severity::Low,
description: "Performance optimization needed".to_string(),
},
];
let regex_strings: Vec<&str> = patterns.iter().map(|p| p.regex.as_str()).collect();
let compiled_patterns =
RegexSet::new(®ex_strings).expect("Failed to compile SATD patterns");
Self {
patterns,
compiled_patterns,
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new_extended() -> Self {
let mut patterns = vec![
DebtPattern {
regex: r"(?i)\b(hack|kludge|smell|xxx)\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Architectural compromise".to_string(),
},
DebtPattern {
regex: r"(?i)\b(fixme|broken|bug)\b".to_string(),
category: DebtCategory::Defect,
severity: Severity::High,
description: "Known defect".to_string(),
},
DebtPattern {
regex: r"(?i)\btodo\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Low,
description: "Missing feature".to_string(),
},
DebtPattern {
regex: r"(?i)\b(security|vuln|vulnerability|cve|xss)\b".to_string(),
category: DebtCategory::Security,
severity: Severity::Critical,
description: "Security concern".to_string(),
},
DebtPattern {
regex: r"(?i)\bperformance\s+(issue|problem)\b".to_string(),
category: DebtCategory::Performance,
severity: Severity::Medium,
description: "Performance issue".to_string(),
},
DebtPattern {
regex: r"(?i)\btest.*\b(disabled|skipped|failing)\b".to_string(),
category: DebtCategory::Test,
severity: Severity::Medium,
description: "Test debt".to_string(),
},
DebtPattern {
regex: r"(?i)\btechnical\s+debt\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Explicit technical debt".to_string(),
},
DebtPattern {
regex: r"(?i)\bcode\s+smell\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Code smell".to_string(),
},
DebtPattern {
regex: r"(?i)\b(workaround|temp|temporary)\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Low,
description: "Temporary solution".to_string(),
},
DebtPattern {
regex: r"(?i)\b(optimize|slow)\b".to_string(),
category: DebtCategory::Performance,
severity: Severity::Low,
description: "Performance optimization needed".to_string(),
},
];
let extended_patterns = vec![
DebtPattern {
regex: r"(?i)\bplaceholder\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Medium,
description: "Placeholder - incomplete implementation".to_string(),
},
DebtPattern {
regex: r"(?i)\bstub\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Medium,
description: "Stub - missing implementation".to_string(),
},
DebtPattern {
regex: r"(?i)\bsimplified\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Low,
description: "Simplified - corners cut".to_string(),
},
DebtPattern {
regex: r"(?i)\b(for\s+)?demonstrat(e|ion)\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Low,
description: "Demo code - not production ready".to_string(),
},
DebtPattern {
regex: r"(?i)\b(mock|dummy|fake)\b".to_string(),
category: DebtCategory::Test,
severity: Severity::Low,
description: "Mock/dummy - fake implementation".to_string(),
},
DebtPattern {
regex: r"(?i)\bhardcoded\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "Hardcoded - missing configuration".to_string(),
},
DebtPattern {
regex: r"(?i)\bfor\s+now\b".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "For now - temporary solution".to_string(),
},
DebtPattern {
regex: r"\bWIP\b".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Medium,
description: "WIP - work in progress".to_string(),
},
DebtPattern {
regex: r"(?i)\b(skip|bypass)\s+(for\s+now|this|validation)\b".to_string(),
category: DebtCategory::Design,
severity: Severity::High,
description: "Skip/bypass - missing validation".to_string(),
},
];
patterns.extend(extended_patterns);
let regex_strings: Vec<&str> = patterns.iter().map(|p| p.regex.as_str()).collect();
let compiled_patterns =
RegexSet::new(®ex_strings).expect("Failed to compile extended SATD patterns");
Self {
patterns,
compiled_patterns,
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn new_strict() -> Self {
let patterns = vec![
DebtPattern {
regex: r"//\s*TODO:\s+.+".to_string(),
category: DebtCategory::Requirement,
severity: Severity::Low,
description: "TODO task marker".to_string(),
},
DebtPattern {
regex: r"//\s*FIXME:\s+.+".to_string(),
category: DebtCategory::Defect,
severity: Severity::High,
description: "FIXME issue marker".to_string(),
},
DebtPattern {
regex: r"//\s*HACK:\s+.+".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "HACK workaround marker".to_string(),
},
DebtPattern {
regex: r"//\s*XXX:\s+.+".to_string(),
category: DebtCategory::Design,
severity: Severity::Medium,
description: "XXX problem marker".to_string(),
},
DebtPattern {
regex: r"//\s*BUG:\s+.+".to_string(),
category: DebtCategory::Defect,
severity: Severity::High,
description: "BUG issue marker".to_string(),
},
];
let regex_strings: Vec<&str> = patterns.iter().map(|p| p.regex.as_str()).collect();
let compiled_patterns =
RegexSet::new(®ex_strings).expect("Failed to compile strict SATD patterns");
Self {
patterns,
compiled_patterns,
}
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn classify_comment(&self, text: &str) -> Option<(DebtCategory, Severity)> {
let matches = self.compiled_patterns.matches(text);
for match_idx in &matches {
if let Some(pattern) = self.patterns.get(match_idx) {
return Some((pattern.category, pattern.severity));
}
}
None
}
#[must_use]
#[provable_contracts_macros::contract("pmat-core.yaml", equation = "check_compliance")]
pub fn adjust_severity(&self, base_severity: Severity, context: &AstContext) -> Severity {
match context.node_type {
AstNodeType::SecurityFunction | AstNodeType::DataValidation => base_severity.escalate(),
AstNodeType::TestFunction | AstNodeType::MockImplementation => base_severity.reduce(),
AstNodeType::Regular if context.complexity > 20 => base_severity.escalate(),
_ => base_severity,
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_new_extended_creates_valid_classifier() {
let classifier = DebtClassifier::new_extended();
assert_eq!(classifier.patterns.len(), 19);
assert!(!classifier.compiled_patterns.is_empty());
}
#[test]
fn test_new_extended_matches_standard_patterns() {
let classifier = DebtClassifier::new_extended();
assert!(classifier.compiled_patterns.is_match("TODO: fix this"));
assert!(classifier.compiled_patterns.is_match("FIXME: broken"));
assert!(classifier.compiled_patterns.is_match("HACK workaround"));
assert!(classifier
.compiled_patterns
.is_match("security vulnerability"));
assert!(classifier.compiled_patterns.is_match("performance issue"));
assert!(classifier.compiled_patterns.is_match("technical debt"));
assert!(classifier.compiled_patterns.is_match("code smell"));
assert!(classifier.compiled_patterns.is_match("temporary fix"));
assert!(classifier.compiled_patterns.is_match("optimize this"));
}
#[test]
fn test_new_extended_matches_euphemism_patterns() {
let classifier = DebtClassifier::new_extended();
assert!(classifier.compiled_patterns.is_match("placeholder value"));
assert!(classifier.compiled_patterns.is_match("stub implementation"));
assert!(classifier.compiled_patterns.is_match("simplified version"));
assert!(classifier.compiled_patterns.is_match("for demonstration"));
assert!(classifier.compiled_patterns.is_match("mock service"));
assert!(classifier.compiled_patterns.is_match("dummy data"));
assert!(classifier.compiled_patterns.is_match("fake response"));
assert!(classifier.compiled_patterns.is_match("hardcoded path"));
assert!(classifier.compiled_patterns.is_match("for now"));
assert!(classifier.compiled_patterns.is_match("WIP"));
assert!(classifier.compiled_patterns.is_match("skip validation"));
assert!(classifier.compiled_patterns.is_match("bypass this"));
}
#[test]
fn test_new_extended_does_not_match_normal_code() {
let classifier = DebtClassifier::new_extended();
assert!(!classifier
.compiled_patterns
.is_match("fn process_request()"));
assert!(!classifier
.compiled_patterns
.is_match("let result = compute()"));
assert!(!classifier.compiled_patterns.is_match("return Ok(value)"));
}
#[test]
fn test_new_extended_has_more_patterns_than_standard() {
let standard = DebtClassifier::new();
let extended = DebtClassifier::new_extended();
assert!(extended.patterns.len() > standard.patterns.len());
}
#[test]
fn test_new_extended_pattern_severities() {
let classifier = DebtClassifier::new_extended();
let security_pattern = classifier
.patterns
.iter()
.find(|p| p.description == "Security concern")
.unwrap();
assert_eq!(security_pattern.severity, Severity::Critical);
let skip_pattern = classifier
.patterns
.iter()
.find(|p| p.description.contains("Skip/bypass"))
.unwrap();
assert_eq!(skip_pattern.severity, Severity::High);
}
#[test]
fn test_new_extended_pattern_categories() {
let classifier = DebtClassifier::new_extended();
let categories: Vec<&DebtCategory> =
classifier.patterns.iter().map(|p| &p.category).collect();
assert!(categories.contains(&&DebtCategory::Design));
assert!(categories.contains(&&DebtCategory::Defect));
assert!(categories.contains(&&DebtCategory::Requirement));
assert!(categories.contains(&&DebtCategory::Security));
assert!(categories.contains(&&DebtCategory::Performance));
assert!(categories.contains(&&DebtCategory::Test));
}
#[test]
fn test_new_strict_is_subset_of_extended() {
let strict = DebtClassifier::new_strict();
let extended = DebtClassifier::new_extended();
assert!(strict.patterns.len() < extended.patterns.len());
}
#[test]
fn test_classify_line_with_extended() {
let classifier = DebtClassifier::new_extended();
let matches: Vec<_> = classifier
.compiled_patterns
.matches("// placeholder for now")
.into_iter()
.collect();
assert!(!matches.is_empty());
}
}