#![cfg_attr(coverage_nightly, coverage(off))]
use anyhow::Result;
use std::collections::HashMap;
#[derive(Debug, Clone)]
pub struct PatternViolation {
pub pattern_name: String,
pub description: String,
pub severity: ViolationSeverity,
pub location: String,
pub suggestion: String,
}
#[derive(Debug, Clone)]
pub enum ViolationSeverity {
Critical,
Major,
Minor,
}
pub trait DesignPattern {
fn apply(&self, code: &str) -> Result<String>;
fn detect_violations(&self, code: &str) -> Vec<PatternViolation>;
fn name(&self) -> &str;
fn description(&self) -> &str;
}
pub struct SingleResponsibilityPattern;
impl DesignPattern for SingleResponsibilityPattern {
fn apply(&self, code: &str) -> Result<String> {
let mut result = code.to_string();
let lines: Vec<&str> = code.lines().collect();
if lines.len() > 20 {
result.push_str("\n\n// Consider extracting helper methods to follow SRP\n");
result.push_str("// Functions should have a single responsibility\n");
}
Ok(result)
}
fn detect_violations(&self, code: &str) -> Vec<PatternViolation> {
let mut violations = Vec::new();
if code.contains("fn ") && code.lines().count() > 30 {
violations.push(PatternViolation {
pattern_name: "Single Responsibility".to_string(),
description: "Function appears to have multiple responsibilities".to_string(),
severity: ViolationSeverity::Major,
location: "function".to_string(),
suggestion: "Consider extracting helper methods".to_string(),
});
}
violations
}
fn name(&self) -> &'static str {
"Single Responsibility Principle"
}
fn description(&self) -> &'static str {
"A function should have only one reason to change"
}
}
pub struct DryPattern;
impl DesignPattern for DryPattern {
fn apply(&self, code: &str) -> Result<String> {
let mut result = code.to_string();
if self.has_duplicates(code) {
result
.push_str("\n\n// Consider extracting common functionality to avoid duplication\n");
}
Ok(result)
}
fn detect_violations(&self, code: &str) -> Vec<PatternViolation> {
let mut violations = Vec::new();
if self.has_duplicates(code) {
violations.push(PatternViolation {
pattern_name: "DRY".to_string(),
description: "Duplicate code detected".to_string(),
severity: ViolationSeverity::Major,
location: "multiple locations".to_string(),
suggestion: "Extract common functionality into shared method".to_string(),
});
}
violations
}
fn name(&self) -> &'static str {
"Don't Repeat Yourself"
}
fn description(&self) -> &'static str {
"Every piece of knowledge must have a single, unambiguous representation"
}
}
impl DryPattern {
fn has_duplicates(&self, code: &str) -> bool {
let lines: Vec<&str> = code.lines().collect();
let mut line_counts = HashMap::new();
for line in lines {
let trimmed = line.trim();
if !trimmed.is_empty() && !trimmed.starts_with("//") {
*line_counts.entry(trimmed).or_insert(0) += 1;
}
}
line_counts.values().any(|&count| count > 1)
}
}
pub struct KissPattern;
impl DesignPattern for KissPattern {
fn apply(&self, code: &str) -> Result<String> {
let mut result = code.to_string();
if self.has_complex_expressions(code) {
result.push_str("\n\n// Consider simplifying complex expressions\n");
result.push_str("// Break down complex logic into simpler parts\n");
}
Ok(result)
}
fn detect_violations(&self, code: &str) -> Vec<PatternViolation> {
let mut violations = Vec::new();
if self.has_complex_expressions(code) {
violations.push(PatternViolation {
pattern_name: "KISS".to_string(),
description: "Overly complex expressions detected".to_string(),
severity: ViolationSeverity::Minor,
location: "expressions".to_string(),
suggestion: "Simplify complex logic".to_string(),
});
}
violations
}
fn name(&self) -> &'static str {
"Keep It Simple Stupid"
}
fn description(&self) -> &'static str {
"Simple solutions are better than complex ones"
}
}
impl KissPattern {
fn has_complex_expressions(&self, code: &str) -> bool {
code.lines().any(|line| {
line.len() > 100
|| line.matches("((").count() > 2
|| line.matches("&&").count() + line.matches("||").count() > 3
})
}
}
pub struct YagniPattern;
impl DesignPattern for YagniPattern {
fn apply(&self, code: &str) -> Result<String> {
let mut result = code.to_string();
if self.has_unused_code(code) {
result.push_str("\n\n// Consider removing unused code (YAGNI principle)\n");
}
Ok(result)
}
fn detect_violations(&self, code: &str) -> Vec<PatternViolation> {
let mut violations = Vec::new();
if self.has_unused_code(code) {
violations.push(PatternViolation {
pattern_name: "YAGNI".to_string(),
description: "Potentially unused code detected".to_string(),
severity: ViolationSeverity::Minor,
location: "various".to_string(),
suggestion: "Remove code that isn't currently needed".to_string(),
});
}
violations
}
fn name(&self) -> &'static str {
"You Aren't Gonna Need It"
}
fn description(&self) -> &'static str {
"Don't add functionality until it's actually needed"
}
}
impl YagniPattern {
fn has_unused_code(&self, code: &str) -> bool {
code.contains("// TODO: future")
|| code.contains("fn future_")
|| code.matches("// ").count() > code.lines().count() / 4
}
}
pub struct DependencyInjectionPattern;
impl DesignPattern for DependencyInjectionPattern {
fn apply(&self, code: &str) -> Result<String> {
let mut result = code.to_string();
if self.has_hard_dependencies(code) {
result.push_str("\n\n// Consider injecting dependencies for better testability\n");
result.push_str("// Use trait objects or generic parameters\n");
}
Ok(result)
}
fn detect_violations(&self, code: &str) -> Vec<PatternViolation> {
let mut violations = Vec::new();
if self.has_hard_dependencies(code) {
violations.push(PatternViolation {
pattern_name: "Dependency Injection".to_string(),
description: "Hard-coded dependencies detected".to_string(),
severity: ViolationSeverity::Major,
location: "constructors/functions".to_string(),
suggestion: "Inject dependencies via parameters or constructors".to_string(),
});
}
violations
}
fn name(&self) -> &'static str {
"Dependency Injection"
}
fn description(&self) -> &'static str {
"Inject dependencies rather than hard-coding them"
}
}
impl DependencyInjectionPattern {
fn has_hard_dependencies(&self, code: &str) -> bool {
code.contains("::new()") && !code.contains("impl")
|| code.contains("std::fs::File::open")
|| code.contains("std::net::TcpStream::connect")
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_single_responsibility_pattern() {
let pattern = SingleResponsibilityPattern;
let good_code = "fn simple() { return 42; }";
let violations = pattern.detect_violations(good_code);
assert!(violations.is_empty());
let bad_code = (0..35)
.map(|i| format!(" line {}", i))
.collect::<Vec<_>>()
.join("\n");
let bad_code = format!("fn complex() {{\n{}\n}}", bad_code);
let violations = pattern.detect_violations(&bad_code);
assert!(!violations.is_empty());
}
#[test]
fn test_dry_pattern() {
let pattern = DryPattern;
let good_code = r#"
fn unique_line1() { println!("one"); }
fn unique_line2() { println!("two"); }
"#;
assert!(!pattern.has_duplicates(good_code));
let bad_code = r#"
fn duplicate1() {
println!("same");
}
fn duplicate2() {
println!("same");
}
"#;
assert!(pattern.has_duplicates(bad_code));
}
#[test]
fn test_kiss_pattern() {
let pattern = KissPattern;
let good_code = "fn simple(x: i32) -> i32 { x + 1 }";
assert!(!pattern.has_complex_expressions(good_code));
let bad_code = "fn complex(x: i32) -> bool { ((x > 0 && x < 100) || (x > 200 && x < 300)) && ((x % 2 == 0) || (x % 3 == 0)) && some_very_long_function_name_that_exceeds_100_characters() }";
assert!(pattern.has_complex_expressions(bad_code));
}
#[test]
fn test_yagni_pattern() {
let pattern = YagniPattern;
let good_code = "fn needed() { actual_work(); }";
assert!(!pattern.has_unused_code(good_code));
let bad_code = r#"
// TODO: future enhancement
fn future_feature() { /* not used yet */ }
// This is commented out
// More commented code
// Even more comments
fn actual() { work(); }
"#;
assert!(pattern.has_unused_code(bad_code));
}
#[test]
fn test_dependency_injection_pattern() {
let pattern = DependencyInjectionPattern;
let good_code = r#"
fn process<T: Reader>(reader: T) -> Result<String> {
reader.read()
}
"#;
assert!(!pattern.has_hard_dependencies(good_code));
let bad_code = r#"
fn process() -> Result<String> {
let file = std::fs::File::open("hardcoded.txt")?;
// hard dependency
}
"#;
assert!(pattern.has_hard_dependencies(bad_code));
}
#[test]
fn test_pattern_application() {
let pattern = SingleResponsibilityPattern;
let code = (0..25)
.map(|i| format!(" line {}", i))
.collect::<Vec<_>>()
.join("\n");
let result = pattern.apply(&code).unwrap();
assert!(result.contains("Consider extracting helper methods"));
}
#[test]
fn test_violation_severity() {
let pattern = DryPattern;
let bad_code = r#"
fn dup1() {
println!("duplicate");
}
fn dup2() {
println!("duplicate");
}
"#;
let violations = pattern.detect_violations(bad_code);
assert!(!violations.is_empty());
match violations[0].severity {
ViolationSeverity::Major => {
}
_ => panic!("Expected Major severity for DRY violation"),
}
}
}
#[cfg_attr(coverage_nightly, coverage(off))]
#[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);
}
}
}