use crate::organization::god_object::scoring::calculate_god_object_score_weighted;
use crate::organization::god_object::{
DetectionType, GodObjectAnalysis, GodObjectConfidence, GodObjectThresholds, SplitAnalysisMethod,
};
use std::collections::HashMap;
pub const HEURISTIC_MAX_FUNCTIONS: usize = 50;
pub const HEURISTIC_MAX_LINES: usize = 2000;
pub const HEURISTIC_MAX_FIELDS: usize = 30;
pub fn fallback_god_object_heuristics(
function_count: usize,
line_count: usize,
field_count: usize,
complexity_sum: u32,
) -> Option<GodObjectAnalysis> {
let is_god_object = function_count > HEURISTIC_MAX_FUNCTIONS
|| line_count > HEURISTIC_MAX_LINES
|| field_count > HEURISTIC_MAX_FIELDS;
if !is_god_object {
return None;
}
let thresholds = GodObjectThresholds::default();
let avg_complexity = if function_count > 0 && complexity_sum > 0 {
complexity_sum as f64 / function_count as f64
} else {
5.0 };
let estimated_resp_count = (function_count / 10).clamp(1, 10);
let responsibilities: Vec<String> = (1..=estimated_resp_count)
.map(|i| format!("responsibility_{}", i))
.collect();
let responsibility_method_counts: HashMap<String, usize> = responsibilities
.iter()
.map(|r| (r.clone(), function_count / estimated_resp_count))
.collect();
let god_object_score = calculate_god_object_score_weighted(
function_count as f64,
field_count,
estimated_resp_count,
line_count,
avg_complexity,
&thresholds,
);
Some(GodObjectAnalysis {
is_god_object: true,
method_count: function_count,
weighted_method_count: None,
field_count,
responsibility_count: estimated_resp_count,
lines_of_code: line_count,
complexity_sum,
god_object_score: god_object_score.max(0.0),
recommended_splits: Vec::new(),
confidence: GodObjectConfidence::Possible,
responsibilities,
responsibility_method_counts,
purity_distribution: None,
module_structure: None,
detection_type: DetectionType::GodFile,
struct_name: None,
struct_line: None,
struct_location: None,
visibility_breakdown: None,
domain_count: 0,
domain_diversity: 0.0,
struct_ratio: 0.0,
analysis_method: SplitAnalysisMethod::None,
cross_domain_severity: None,
domain_diversity_metrics: None,
aggregated_entropy: None,
aggregated_error_swallowing_count: None,
aggregated_error_swallowing_patterns: None,
layering_impact: None,
anti_pattern_report: None,
complexity_metrics: None, trait_method_summary: None, })
}
pub fn detect_from_content(content: &str) -> Option<GodObjectAnalysis> {
let line_count = content.lines().count();
let function_count = content.matches("fn ").count()
+ content.matches("def ").count()
+ content.matches("function ").count();
let field_count = content.matches("pub ").count() + content.matches("self.").count() / 3;
fallback_god_object_heuristics(function_count, line_count, field_count, 0)
}
pub fn fallback_with_preserved_analysis(
function_count: usize,
line_count: usize,
complexity_sum: u32,
existing_analysis: Option<&GodObjectAnalysis>,
) -> Option<GodObjectAnalysis> {
if function_count <= HEURISTIC_MAX_FUNCTIONS && line_count <= HEURISTIC_MAX_LINES {
return None;
}
let thresholds = GodObjectThresholds::default();
let avg_complexity = if function_count > 0 && complexity_sum > 0 {
complexity_sum as f64 / function_count as f64
} else {
5.0 };
let (responsibilities, responsibility_method_counts, responsibility_count) =
if let Some(analysis) = existing_analysis {
if !analysis.responsibilities.is_empty() {
(
analysis.responsibilities.clone(),
analysis.responsibility_method_counts.clone(),
analysis.responsibility_count,
)
} else {
estimate_responsibilities(function_count)
}
} else {
estimate_responsibilities(function_count)
};
let god_object_score = calculate_god_object_score_weighted(
function_count as f64,
0, responsibility_count,
line_count,
avg_complexity,
&thresholds,
);
Some(GodObjectAnalysis {
is_god_object: true,
method_count: function_count,
weighted_method_count: None,
field_count: 0,
responsibility_count,
lines_of_code: line_count,
complexity_sum,
god_object_score: god_object_score.max(0.0),
recommended_splits: Vec::new(),
confidence: GodObjectConfidence::Probable,
responsibilities,
responsibility_method_counts,
purity_distribution: None,
module_structure: None,
detection_type: DetectionType::GodFile,
struct_name: None,
struct_line: None,
struct_location: None,
visibility_breakdown: None,
domain_count: 0,
domain_diversity: 0.0,
struct_ratio: 0.0,
analysis_method: SplitAnalysisMethod::None,
cross_domain_severity: None,
domain_diversity_metrics: None,
aggregated_entropy: None,
aggregated_error_swallowing_count: None,
aggregated_error_swallowing_patterns: None,
layering_impact: None,
anti_pattern_report: None,
complexity_metrics: None, trait_method_summary: None, })
}
fn estimate_responsibilities(
function_count: usize,
) -> (Vec<String>, HashMap<String, usize>, usize) {
let estimated_resp_count = (function_count / 10).clamp(1, 10);
let responsibilities: Vec<String> = (1..=estimated_resp_count)
.map(|i| format!("responsibility_{}", i))
.collect();
let responsibility_method_counts: HashMap<String, usize> = responsibilities
.iter()
.map(|r| (r.clone(), function_count / estimated_resp_count))
.collect();
(
responsibilities,
responsibility_method_counts,
estimated_resp_count,
)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_small_file_not_god_object() {
let result = fallback_god_object_heuristics(10, 500, 5, 0);
assert!(result.is_none());
}
#[test]
fn test_many_functions_is_god_object() {
let result = fallback_god_object_heuristics(60, 1000, 10, 0);
assert!(result.is_some());
let analysis = result.unwrap();
assert!(analysis.is_god_object);
assert_eq!(analysis.method_count, 60);
}
#[test]
fn test_many_lines_is_god_object() {
let result = fallback_god_object_heuristics(20, 3000, 10, 0);
assert!(result.is_some());
let analysis = result.unwrap();
assert!(analysis.is_god_object);
assert_eq!(analysis.lines_of_code, 3000);
}
#[test]
fn test_many_fields_is_god_object() {
let result = fallback_god_object_heuristics(20, 500, 40, 0);
assert!(result.is_some());
let analysis = result.unwrap();
assert!(analysis.is_god_object);
assert_eq!(analysis.field_count, 40);
}
#[test]
fn test_detect_from_content() {
let content = r#"
fn foo() {}
fn bar() {}
fn baz() {}
pub struct Thing { pub field: i32 }
"#;
let result = detect_from_content(content);
assert!(result.is_none());
}
#[test]
fn test_detect_from_content_large() {
let functions: String = (0..60).map(|i| format!("fn func_{}() {{}}\n", i)).collect();
let result = detect_from_content(&functions);
assert!(result.is_some());
}
#[test]
fn test_responsibilities_estimated() {
let result = fallback_god_object_heuristics(100, 3000, 20, 0).unwrap();
assert_eq!(result.responsibility_count, 10);
assert_eq!(result.responsibilities.len(), 10);
}
}