use super::TypeVisitor;
use crate::analysis::FunctionCounts;
use crate::organization::{
aggregate_weighted_complexity, calculate_avg_complexity, calculate_complexity_weight,
calculate_god_object_score, calculate_god_object_score_weighted,
group_methods_by_responsibility, DetectionType, FunctionComplexityInfo,
FunctionVisibilityBreakdown, GodObjectThresholds, PurityAnalyzer, PurityDistribution,
PurityLevel, StructMetrics,
};
use std::collections::HashMap;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum VisibilityClass {
Public,
PubCrate,
PubSuper,
Private,
}
fn classify_visibility(vis: &syn::Visibility) -> VisibilityClass {
match vis {
syn::Visibility::Public(_) => VisibilityClass::Public,
syn::Visibility::Restricted(r) => r
.path
.get_ident()
.map(|ident| {
if ident == "crate" {
VisibilityClass::PubCrate
} else if ident == "super" {
VisibilityClass::PubSuper
} else {
VisibilityClass::Private
}
})
.unwrap_or(VisibilityClass::Private),
syn::Visibility::Inherited => VisibilityClass::Private,
}
}
fn apply_visibility_to_breakdown(
breakdown: &mut FunctionVisibilityBreakdown,
class: VisibilityClass,
) {
match class {
VisibilityClass::Public => breakdown.public += 1,
VisibilityClass::PubCrate => breakdown.pub_crate += 1,
VisibilityClass::PubSuper => breakdown.pub_super += 1,
VisibilityClass::Private => breakdown.private += 1,
}
}
pub fn build_per_struct_metrics(visitor: &TypeVisitor) -> Vec<StructMetrics> {
visitor
.types
.values()
.map(|type_analysis| {
let responsibilities = group_methods_by_responsibility(&type_analysis.methods);
StructMetrics {
name: type_analysis.name.clone(),
method_count: type_analysis.method_count, field_count: type_analysis.field_count,
responsibilities: responsibilities.keys().cloned().collect(),
line_span: (
type_analysis.location.line,
type_analysis
.location
.end_line
.unwrap_or(type_analysis.location.line),
),
}
})
.collect()
}
pub fn calculate_weighted_metrics(
visitor: &TypeVisitor,
detection_type: &DetectionType,
) -> (f64, f64, f64, Option<PurityDistribution>) {
let relevant_complexity: Vec<_> = match detection_type {
DetectionType::GodClass => {
visitor
.function_complexity
.iter()
.filter(|fc| !fc.is_test)
.cloned()
.collect()
}
DetectionType::GodFile | DetectionType::GodModule => {
visitor.function_complexity.clone()
}
};
let weighted_method_count = aggregate_weighted_complexity(&relevant_complexity);
let avg_complexity = calculate_avg_complexity(&relevant_complexity);
let (purity_weighted_count, purity_distribution) = if !visitor.function_items.is_empty() {
let relevant_items: Vec<_> = match detection_type {
DetectionType::GodClass => {
visitor
.function_items
.iter()
.filter(|item| {
!visitor
.function_complexity
.iter()
.find(|fc| item.sig.ident == fc.name)
.map(|fc| fc.is_test)
.unwrap_or(false)
})
.cloned()
.collect()
}
DetectionType::GodFile | DetectionType::GodModule => {
visitor.function_items.clone()
}
};
calculate_purity_weights(&relevant_items, &relevant_complexity)
} else {
(weighted_method_count, None)
};
(
weighted_method_count,
avg_complexity,
purity_weighted_count,
purity_distribution,
)
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_final_god_object_score(
purity_weighted_count: f64,
weighted_method_count: f64,
total_methods: usize,
total_fields: usize,
responsibility_count: usize,
lines_of_code: usize,
avg_complexity: f64,
purity_distribution: &Option<PurityDistribution>,
has_complexity_data: bool,
thresholds: &GodObjectThresholds,
module_structure: Option<&crate::analysis::ModuleStructure>,
) -> (f64, bool) {
let mut god_object_score = if purity_distribution.is_some() {
calculate_god_object_score_weighted(
purity_weighted_count,
total_fields,
responsibility_count,
lines_of_code,
avg_complexity,
thresholds,
)
} else if has_complexity_data {
calculate_god_object_score_weighted(
weighted_method_count,
total_fields,
responsibility_count,
lines_of_code,
avg_complexity,
thresholds,
)
} else {
calculate_god_object_score(
total_methods,
total_fields,
responsibility_count,
lines_of_code,
thresholds,
)
};
if let Some(structure) = module_structure {
if let Some(facade_info) = &structure.facade_info {
god_object_score = crate::priority::scoring::adjust_score_for_facade(
god_object_score,
facade_info,
total_methods,
lines_of_code,
);
}
}
let is_god_object = god_object_score >= 70.0;
(god_object_score, is_god_object)
}
pub fn calculate_purity_weights(
function_items: &[syn::ItemFn],
function_complexity: &[FunctionComplexityInfo],
) -> (f64, Option<PurityDistribution>) {
if function_items.is_empty() {
return (0.0, None);
}
let complexity_map: HashMap<String, u32> = function_complexity
.iter()
.map(|f| (f.name.clone(), f.cyclomatic_complexity))
.collect();
let mut pure_count = 0;
let mut probably_pure_count = 0;
let mut impure_count = 0;
let mut pure_weight = 0.0;
let mut probably_pure_weight = 0.0;
let mut impure_weight = 0.0;
for func in function_items {
let name = func.sig.ident.to_string();
let purity_level = PurityAnalyzer::analyze(func);
let complexity = complexity_map.get(&name).copied().unwrap_or(1);
let complexity_weight = calculate_complexity_weight(complexity);
let purity_weight_multiplier = purity_level.weight_multiplier();
let total_weight = complexity_weight * purity_weight_multiplier;
match purity_level {
PurityLevel::Pure => {
pure_count += 1;
pure_weight += total_weight;
}
PurityLevel::ProbablyPure => {
probably_pure_count += 1;
probably_pure_weight += total_weight;
}
PurityLevel::Impure => {
impure_count += 1;
impure_weight += total_weight;
}
}
}
let total_weighted = pure_weight + probably_pure_weight + impure_weight;
let distribution = PurityDistribution {
pure_count,
probably_pure_count,
impure_count,
pure_weight_contribution: pure_weight,
probably_pure_weight_contribution: probably_pure_weight,
impure_weight_contribution: impure_weight,
};
(total_weighted, Some(distribution))
}
pub fn calculate_visibility_breakdown(
visitor: &TypeVisitor,
method_names: &[String],
) -> FunctionVisibilityBreakdown {
let mut breakdown = FunctionVisibilityBreakdown::new();
for func_item in &visitor.function_items {
let name = func_item.sig.ident.to_string();
if method_names.contains(&name) {
let class = classify_visibility(&func_item.vis);
apply_visibility_to_breakdown(&mut breakdown, class);
}
}
for type_info in visitor.types.values() {
for method_name in &type_info.methods {
if !method_names.contains(method_name) {
continue;
}
if let Some(vis) = visitor.method_visibility.get(method_name) {
let class = classify_visibility(vis);
apply_visibility_to_breakdown(&mut breakdown, class);
} else {
breakdown.private += 1;
}
}
}
breakdown
}
pub fn integrate_visibility_into_counts(
original_counts: &FunctionCounts,
breakdown: &FunctionVisibilityBreakdown,
_total_methods: usize,
) -> FunctionCounts {
FunctionCounts {
module_level_functions: original_counts.module_level_functions,
impl_methods: original_counts.impl_methods,
trait_methods: original_counts.trait_methods,
nested_module_functions: original_counts.nested_module_functions,
public_functions: breakdown.public,
private_functions: breakdown.private + breakdown.pub_crate + breakdown.pub_super,
}
}