use crate::organization::god_object::ast_visitor::TypeAnalysis;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum StructPattern {
Config,
DataTransferObject,
AggregateRoot,
Standard,
}
#[derive(Debug, Clone)]
pub struct PatternAnalysis {
pub pattern: StructPattern,
pub confidence: f64,
pub evidence: Vec<String>,
pub skip_god_object_check: bool,
}
pub fn detect_pattern(type_analysis: &TypeAnalysis, responsibilities: usize) -> PatternAnalysis {
if let Some(analysis) = detect_config_pattern(type_analysis, responsibilities) {
return analysis;
}
if let Some(analysis) = detect_dto_pattern(type_analysis, responsibilities) {
return analysis;
}
if let Some(analysis) = detect_aggregate_root_pattern(type_analysis, responsibilities) {
return analysis;
}
PatternAnalysis {
pattern: StructPattern::Standard,
confidence: 1.0,
evidence: vec![],
skip_god_object_check: false,
}
}
fn detect_config_pattern(
type_analysis: &TypeAnalysis,
responsibilities: usize,
) -> Option<PatternAnalysis> {
let mut evidence = Vec::new();
let mut confidence = 0.0;
let name_lower = type_analysis.name.to_lowercase();
if name_lower.contains("config")
|| name_lower.contains("settings")
|| name_lower.contains("options")
{
evidence.push("Name indicates configuration struct".to_string());
confidence += 0.3;
}
let factory_methods = ["strict", "balanced", "lenient", "default", "new"];
let has_factory = type_analysis
.methods
.iter()
.any(|m| factory_methods.contains(&m.as_str()));
if has_factory {
let found: Vec<_> = type_analysis
.methods
.iter()
.filter(|m| factory_methods.contains(&m.as_str()))
.collect();
evidence.push(format!("Has factory methods: {:?}", found));
confidence += 0.4;
}
if type_analysis.field_count <= 10 && type_analysis.method_count <= 10 {
evidence.push(format!(
"Reasonable size: {} fields, {} methods",
type_analysis.field_count, type_analysis.method_count
));
confidence += 0.2;
}
if responsibilities <= 1 {
evidence.push("Single responsibility (configuration)".to_string());
confidence += 0.1;
}
if confidence >= 0.6 {
Some(PatternAnalysis {
pattern: StructPattern::Config,
confidence,
evidence,
skip_god_object_check: true,
})
} else {
None
}
}
fn detect_dto_pattern(
type_analysis: &TypeAnalysis,
responsibilities: usize,
) -> Option<PatternAnalysis> {
let mut evidence = Vec::new();
let mut confidence = 0.0;
if type_analysis.field_count >= 15 {
evidence.push(format!("High field count: {}", type_analysis.field_count));
confidence += 0.3;
}
if type_analysis.method_count <= 3 {
evidence.push(format!(
"Minimal methods ({}), indicating data container",
type_analysis.method_count
));
confidence += 0.3;
}
let ratio = type_analysis.method_count as f64 / type_analysis.field_count.max(1) as f64;
if ratio < 0.2 {
evidence.push(format!(
"Low method-to-field ratio ({:.2}), data-heavy",
ratio
));
confidence += 0.2;
}
if responsibilities <= 1 {
evidence.push("Single conceptual responsibility".to_string());
confidence += 0.2;
}
let name_lower = type_analysis.name.to_lowercase();
let dto_suffixes = [
"data", "dto", "item", "record", "result", "metrics", "analysis",
];
if dto_suffixes.iter().any(|s| name_lower.ends_with(s)) {
evidence.push(format!("Name pattern suggests DTO: {}", type_analysis.name));
confidence += 0.1;
}
if confidence >= 0.7 {
Some(PatternAnalysis {
pattern: StructPattern::DataTransferObject,
confidence,
evidence,
skip_god_object_check: true,
})
} else {
None
}
}
fn detect_aggregate_root_pattern(
type_analysis: &TypeAnalysis,
responsibilities: usize,
) -> Option<PatternAnalysis> {
let mut evidence = Vec::new();
let mut confidence = 0.0;
if responsibilities > 1 {
return None; }
evidence.push("Single responsibility detected".to_string());
confidence += 0.4;
if type_analysis.field_count < 10 {
return None; }
evidence.push(format!(
"Complex domain entity: {} fields",
type_analysis.field_count
));
confidence += 0.2;
if type_analysis.method_count >= 5 && type_analysis.method_count <= 20 {
evidence.push(format!(
"Moderate method count ({}), within domain complexity",
type_analysis.method_count
));
confidence += 0.2;
}
if confidence >= 0.6 {
Some(PatternAnalysis {
pattern: StructPattern::AggregateRoot,
confidence,
evidence,
skip_god_object_check: false, })
} else {
None
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::common::{LocationConfidence, SourceLocation};
fn make_type_analysis(
name: &str,
methods: Vec<&str>,
method_count: usize,
field_count: usize,
) -> TypeAnalysis {
TypeAnalysis {
name: name.to_string(),
method_count,
field_count,
methods: methods.into_iter().map(String::from).collect(),
fields: vec![],
field_types: vec![],
trait_implementations: 0,
location: SourceLocation {
line: 1,
column: Some(1),
end_line: None,
end_column: None,
confidence: LocationConfidence::Exact,
},
impl_locations: vec![],
}
}
#[test]
fn test_config_pattern_detected() {
let type_analysis = make_type_analysis(
"FunctionalAnalysisConfig",
vec!["strict", "balanced", "lenient", "should_analyze"],
4,
5,
);
let analysis = detect_pattern(&type_analysis, 1);
assert_eq!(analysis.pattern, StructPattern::Config);
assert!(analysis.confidence >= 0.6);
assert!(analysis.skip_god_object_check);
}
#[test]
fn test_dto_pattern_detected() {
let type_analysis =
make_type_analysis("UnifiedDebtItem", vec!["with_pattern_analysis"], 1, 35);
let analysis = detect_pattern(&type_analysis, 1);
assert_eq!(analysis.pattern, StructPattern::DataTransferObject);
assert!(analysis.confidence >= 0.7);
assert!(analysis.skip_god_object_check);
}
#[test]
fn test_genuine_god_object_not_dto() {
let type_analysis = make_type_analysis(
"UserManager",
vec![
"create_user",
"delete_user",
"send_email",
"log_activity",
"validate_input",
"render_template",
],
25,
20,
);
let analysis = detect_pattern(&type_analysis, 5); assert_eq!(analysis.pattern, StructPattern::Standard);
assert!(!analysis.skip_god_object_check);
}
#[test]
fn test_aggregate_root_pattern() {
let type_analysis = make_type_analysis(
"Order",
vec!["add_item", "calculate_total", "apply_discount", "validate"],
8,
12,
);
let analysis = detect_pattern(&type_analysis, 1);
assert_eq!(analysis.pattern, StructPattern::AggregateRoot);
assert!(!analysis.skip_god_object_check); }
#[test]
fn test_standard_struct() {
let type_analysis = make_type_analysis("Helper", vec!["process"], 5, 3);
let analysis = detect_pattern(&type_analysis, 1);
assert_eq!(analysis.pattern, StructPattern::Standard);
}
}