use super::classifier::group_methods_by_responsibility;
use crate::common::UnifiedLocationExtractor;
use crate::organization::ResponsibilityGroup;
use crate::organization::{MaintainabilityImpact, OrganizationAntiPattern, OrganizationDetector};
pub struct GodObjectDetector {
pub(crate) max_methods: usize,
pub(crate) max_fields: usize,
pub(crate) max_responsibilities: usize,
pub(crate) location_extractor: Option<UnifiedLocationExtractor>,
}
impl Default for GodObjectDetector {
fn default() -> Self {
Self {
max_methods: 15,
max_fields: 10,
max_responsibilities: 3,
location_extractor: None,
}
}
}
impl GodObjectDetector {
pub fn new() -> Self {
Self::default()
}
pub fn with_source_content(source_content: &str) -> Self {
Self {
max_methods: 15,
max_fields: 10,
max_responsibilities: 3,
location_extractor: Some(UnifiedLocationExtractor::new(source_content)),
}
}
pub fn max_methods(&self) -> usize {
self.max_methods
}
pub fn max_fields(&self) -> usize {
self.max_fields
}
pub fn max_responsibilities(&self) -> usize {
self.max_responsibilities
}
pub fn classify_god_object_impact(
method_count: usize,
field_count: usize,
) -> MaintainabilityImpact {
match () {
_ if method_count > 30 || field_count > 20 => MaintainabilityImpact::Critical,
_ if method_count > 20 || field_count > 15 => MaintainabilityImpact::High,
_ => MaintainabilityImpact::Medium,
}
}
}
impl OrganizationDetector for GodObjectDetector {
fn detect_anti_patterns(&self, file: &syn::File) -> Vec<OrganizationAntiPattern> {
use super::ast_visitor::TypeVisitor;
use syn::visit::Visit;
let mut patterns = Vec::new();
let mut visitor = TypeVisitor::with_location_extractor(self.location_extractor.clone());
visitor.visit_file(file);
let mut sorted_types: Vec<_> = visitor.types.into_iter().collect();
sorted_types.sort_by(|a, b| a.0.cmp(&b.0));
for (type_name, type_info) in sorted_types {
let method_names = &type_info.methods;
let responsibilities = group_methods_by_responsibility(method_names);
let is_god = type_info.method_count > self.max_methods
|| type_info.field_count > self.max_fields
|| responsibilities.len() > self.max_responsibilities;
if is_god {
let mut sorted_responsibilities: Vec<_> = responsibilities.into_iter().collect();
sorted_responsibilities.sort_by(|a, b| a.0.cmp(&b.0));
let suggested_split: Vec<ResponsibilityGroup> = sorted_responsibilities
.into_iter()
.map(|(responsibility, methods)| ResponsibilityGroup {
name: responsibility.clone(),
methods,
fields: vec![], responsibility,
})
.collect();
patterns.push(OrganizationAntiPattern::GodObject {
type_name: type_name.clone(),
method_count: type_info.method_count,
field_count: type_info.field_count,
responsibility_count: suggested_split.len(),
suggested_split,
location: type_info.location,
});
}
}
patterns
}
fn detector_name(&self) -> &'static str {
"GodObjectDetector"
}
fn estimate_maintainability_impact(
&self,
pattern: &OrganizationAntiPattern,
) -> MaintainabilityImpact {
match pattern {
OrganizationAntiPattern::GodObject {
method_count,
field_count,
..
} => Self::classify_god_object_impact(*method_count, *field_count),
_ => MaintainabilityImpact::Low,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_detector_creation() {
let detector = GodObjectDetector::new();
assert_eq!(detector.max_methods, 15);
assert_eq!(detector.max_fields, 10);
assert_eq!(detector.max_responsibilities, 3);
}
#[test]
fn test_detector_with_source_content() {
let content = "struct Foo {}";
let detector = GodObjectDetector::with_source_content(content);
assert!(detector.location_extractor.is_some());
}
#[test]
fn test_detector_thresholds() {
let detector = GodObjectDetector::new();
assert_eq!(detector.max_methods(), 15);
assert_eq!(detector.max_fields(), 10);
assert_eq!(detector.max_responsibilities(), 3);
}
#[test]
fn test_detector_name() {
let detector = GodObjectDetector::new();
assert_eq!(detector.detector_name(), "GodObjectDetector");
}
#[test]
fn test_cohesion_gate_allows_non_cohesive_structs() {
use crate::organization::god_object::classifier::{
calculate_domain_cohesion, is_cohesive_struct,
};
let struct_name = "ApplicationManager";
let methods: Vec<String> = vec![
"new",
"parse_json",
"parse_xml",
"render_html",
"render_pdf",
"validate_email",
"validate_phone",
"send_email",
"send_sms",
"save_to_database",
"load_from_database",
"connect_to_server",
"disconnect_from_server",
"handle_request",
"handle_error",
"log_event",
"track_metrics",
]
.into_iter()
.map(String::from)
.collect();
let cohesion = calculate_domain_cohesion(struct_name, &methods);
let is_cohesive = is_cohesive_struct(struct_name, &methods);
assert!(
cohesion < 0.1,
"ApplicationManager with unrelated methods should have near-zero cohesion, got {}",
cohesion
);
assert!(
!is_cohesive,
"ApplicationManager with unrelated methods should NOT be cohesive"
);
let cohesive_struct = "ModuleTracker";
let cohesive_methods: Vec<String> = vec![
"new",
"analyze_module",
"get_module_calls",
"resolve_module_call",
"track_module",
"is_module_public",
]
.into_iter()
.map(String::from)
.collect();
let cohesive_cohesion = calculate_domain_cohesion(cohesive_struct, &cohesive_methods);
let cohesive_is_cohesive = is_cohesive_struct(cohesive_struct, &cohesive_methods);
assert!(
cohesive_cohesion > 0.5,
"ModuleTracker with module-related methods should have high cohesion, got {}",
cohesive_cohesion
);
assert!(
cohesive_is_cohesive,
"ModuleTracker with module-related methods SHOULD be cohesive"
);
}
#[test]
fn test_loc_includes_impl_blocks() {
use crate::organization::god_object::ast_visitor::TypeVisitor;
use syn::visit::Visit;
let content = r#"
pub struct Foo {
a: i32,
b: i32,
}
impl Foo {
pub fn method1(&self) -> i32 {
self.a + self.b
}
pub fn method2(&self) -> i32 {
self.a * self.b
}
}
"#;
let ast = syn::parse_file(content).expect("parse");
let detector = GodObjectDetector::with_source_content(content);
let mut visitor = TypeVisitor::with_location_extractor(detector.location_extractor.clone());
visitor.visit_file(&ast);
let foo = visitor.types.get("Foo").expect("Foo should be found");
assert!(
!foo.impl_locations.is_empty(),
"impl_locations should be populated"
);
let struct_loc = foo
.location
.end_line
.unwrap_or(foo.location.line)
.saturating_sub(foo.location.line)
+ 1;
let impl_loc: usize = foo
.impl_locations
.iter()
.map(|loc| loc.end_line.unwrap_or(loc.line).saturating_sub(loc.line) + 1)
.sum();
let total_loc = struct_loc + impl_loc;
assert!(
total_loc >= 10,
"LOC should include impl blocks, got {} (struct={}, impl={})",
total_loc,
struct_loc,
impl_loc
);
}
#[test]
fn test_loc_with_multiple_impl_blocks() {
use crate::organization::god_object::ast_visitor::TypeVisitor;
use syn::visit::Visit;
let content = r#"
pub struct Bar { a: i32 }
impl Bar {
pub fn new() -> Self { Self { a: 0 } }
}
impl Default for Bar {
fn default() -> Self { Self::new() }
}
impl Clone for Bar {
fn clone(&self) -> Self { Self { a: self.a } }
}
"#;
let ast = syn::parse_file(content).expect("parse");
let detector = GodObjectDetector::with_source_content(content);
let mut visitor = TypeVisitor::with_location_extractor(detector.location_extractor.clone());
visitor.visit_file(&ast);
let bar = visitor.types.get("Bar").expect("Bar should be found");
assert!(
bar.impl_locations.len() >= 3,
"Should track all 3 impl blocks, got {}",
bar.impl_locations.len()
);
let struct_loc = bar
.location
.end_line
.unwrap_or(bar.location.line)
.saturating_sub(bar.location.line)
+ 1;
let impl_loc: usize = bar
.impl_locations
.iter()
.map(|loc| loc.end_line.unwrap_or(loc.line).saturating_sub(loc.line) + 1)
.sum();
let total_loc = struct_loc + impl_loc;
assert!(
total_loc >= 8,
"LOC should include all impl blocks, got {} (struct={}, impl={})",
total_loc,
struct_loc,
impl_loc
);
}
#[test]
fn test_classify_god_object_impact() {
assert_eq!(
GodObjectDetector::classify_god_object_impact(35, 5),
MaintainabilityImpact::Critical
);
assert_eq!(
GodObjectDetector::classify_god_object_impact(10, 25),
MaintainabilityImpact::Critical
);
assert_eq!(
GodObjectDetector::classify_god_object_impact(25, 5),
MaintainabilityImpact::High
);
assert_eq!(
GodObjectDetector::classify_god_object_impact(10, 18),
MaintainabilityImpact::High
);
assert_eq!(
GodObjectDetector::classify_god_object_impact(15, 10),
MaintainabilityImpact::Medium
);
}
}