use super::{FileDebtItem, UnifiedAnalysis, UnifiedDebtItem};
use crate::data_flow::{DataFlowGraph, IoOperation, PurityInfo};
use crate::priority::call_graph::FunctionId;
use std::cmp::Ordering;
fn compare_debt_items_by_score(a: &UnifiedDebtItem, b: &UnifiedDebtItem) -> Ordering {
b.unified_score
.final_score
.partial_cmp(&a.unified_score.final_score)
.unwrap_or(Ordering::Equal)
.then_with(|| a.location.file.cmp(&b.location.file))
.then_with(|| a.location.line.cmp(&b.location.line))
.then_with(|| a.location.function.cmp(&b.location.function))
}
fn compare_file_items_by_score(a: &FileDebtItem, b: &FileDebtItem) -> Ordering {
b.score
.partial_cmp(&a.score)
.unwrap_or(Ordering::Equal)
.then_with(|| a.metrics.path.cmp(&b.metrics.path))
}
pub trait UnifiedAnalysisUtils {
fn timings(&self) -> Option<&crate::builders::parallel_unified_analysis::AnalysisPhaseTimings>;
fn add_file_item(&mut self, item: FileDebtItem);
fn add_item(&mut self, item: UnifiedDebtItem);
fn sort_by_priority(&mut self);
fn data_flow_graph(&self) -> &DataFlowGraph;
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph;
fn populate_purity_analysis(&mut self, metrics: &[crate::core::FunctionMetrics]);
fn add_io_operation(&mut self, func_id: FunctionId, operation: IoOperation);
fn add_variable_dependencies(
&mut self,
func_id: FunctionId,
variables: std::collections::HashSet<String>,
);
fn apply_file_context_adjustments(
&mut self,
file_contexts: &std::collections::HashMap<std::path::PathBuf, crate::analysis::FileContext>,
);
}
impl UnifiedAnalysisUtils for UnifiedAnalysis {
fn timings(&self) -> Option<&crate::builders::parallel_unified_analysis::AnalysisPhaseTimings> {
self.timings.as_ref()
}
fn add_file_item(&mut self, item: FileDebtItem) {
let min_score = crate::config::get_minimum_debt_score();
if item.score <= 0.0 || item.score < min_score {
return;
}
let is_duplicate = self
.file_items
.iter()
.any(|existing| existing.metrics.path == item.metrics.path);
if !is_duplicate {
self.file_items.push_back(item);
}
}
fn add_item(&mut self, item: UnifiedDebtItem) {
use crate::priority::filter_config::ItemFilterConfig;
use crate::priority::filter_predicates::*;
self.stats.total_items_processed += 1;
if item.unified_score.final_score <= 0.0 {
self.stats.filtered_by_score += 1;
return;
}
let is_god_object = item
.god_object_indicators
.as_ref()
.is_some_and(|indicators| indicators.is_god_object);
if !is_god_object {
let config = ItemFilterConfig::from_environment();
if !meets_score_threshold(&item, config.min_score) {
self.stats.filtered_by_score += 1;
return;
}
if !meets_risk_threshold(&item, config.min_risk) {
self.stats.filtered_by_risk += 1;
return;
}
if !meets_complexity_thresholds(&item, config.min_cyclomatic, config.min_cognitive) {
self.stats.filtered_by_complexity += 1;
return;
}
}
if self
.items
.iter()
.any(|existing| is_duplicate_of(&item, existing))
{
self.stats.filtered_as_duplicate += 1;
return;
}
self.items.push_back(item);
self.stats.items_added += 1;
}
fn sort_by_priority(&mut self) {
self.items.sort_by(compare_debt_items_by_score);
self.file_items.sort_by(compare_file_items_by_score);
}
fn data_flow_graph(&self) -> &DataFlowGraph {
&self.data_flow_graph
}
fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph {
&mut self.data_flow_graph
}
fn populate_purity_analysis(&mut self, metrics: &[crate::core::FunctionMetrics]) {
for metric in metrics {
let func_id = FunctionId::new(metric.file.clone(), metric.name.clone(), metric.line);
let purity_info = PurityInfo {
is_pure: metric.is_pure.unwrap_or(false),
confidence: metric.purity_confidence.unwrap_or(0.0),
impurity_reasons: if !metric.is_pure.unwrap_or(false) {
vec!["Function may have side effects".to_string()]
} else {
vec![]
},
};
self.data_flow_graph.set_purity_info(func_id, purity_info);
}
}
fn add_io_operation(&mut self, func_id: FunctionId, operation: IoOperation) {
self.data_flow_graph.add_io_operation(func_id, operation);
}
fn add_variable_dependencies(
&mut self,
func_id: FunctionId,
variables: std::collections::HashSet<String>,
) {
self.data_flow_graph
.add_variable_dependencies(func_id, variables);
}
fn apply_file_context_adjustments(
&mut self,
file_contexts: &std::collections::HashMap<std::path::PathBuf, crate::analysis::FileContext>,
) {
use crate::priority::scoring::file_context_scoring::apply_context_adjustments;
self.items = self
.items
.iter()
.map(|item| {
if let Some(context) = file_contexts.get(&item.location.file) {
let adjusted_score =
apply_context_adjustments(item.unified_score.final_score, context);
let mut adjusted_item = item.clone();
adjusted_item.unified_score.final_score = adjusted_score.max(0.0);
adjusted_item.file_context = Some(context.clone());
adjusted_item
} else {
item.clone()
}
})
.collect();
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::organization::{
DetectionType, GodObjectAnalysis, GodObjectConfidence, SplitAnalysisMethod,
};
use crate::priority::call_graph::CallGraph;
use crate::priority::{
ActionableRecommendation, DebtType, FunctionRole, ImpactMetrics, Location, UnifiedScore,
};
use std::collections::HashMap;
use std::path::PathBuf;
fn create_god_object_item(score: f64) -> UnifiedDebtItem {
UnifiedDebtItem {
location: Location {
file: PathBuf::from("test.rs"),
function: "[file-scope]".to_string(),
line: 1,
},
debt_type: DebtType::GodObject {
methods: 50,
fields: Some(20),
responsibilities: 10,
god_object_score: 85.0,
lines: 500,
},
unified_score: UnifiedScore {
final_score: score,
complexity_factor: 0.0,
coverage_factor: 0.0,
dependency_factor: 0.0,
role_multiplier: 1.0,
base_score: None,
exponential_factor: None,
risk_boost: None,
pre_adjustment_score: None,
adjustment_applied: None,
purity_factor: None,
refactorability_factor: None,
pattern_factor: None,
debt_adjustment: None,
pre_normalization_score: None,
structural_multiplier: Some(1.0),
has_coverage_data: false,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
},
cyclomatic_complexity: 65,
cognitive_complexity: 6,
function_role: FunctionRole::PureLogic,
recommendation: ActionableRecommendation {
primary_action: "Split".to_string(),
rationale: "God object".to_string(),
implementation_steps: vec![],
related_items: vec![],
steps: None,
estimated_effort_hours: None,
},
expected_impact: ImpactMetrics {
coverage_improvement: 0.0,
lines_reduction: 0,
complexity_reduction: 0.0,
risk_reduction: 0.0,
},
transitive_coverage: None,
upstream_dependencies: 0,
downstream_dependencies: 0,
upstream_callers: vec![],
downstream_callees: vec![],
upstream_production_callers: vec![],
upstream_test_callers: vec![],
production_blast_radius: 0,
nesting_depth: 0,
function_length: 500,
is_pure: None,
purity_confidence: None,
purity_level: None,
god_object_indicators: Some(GodObjectAnalysis {
is_god_object: true,
method_count: 50,
weighted_method_count: None,
field_count: 20,
responsibility_count: 10,
lines_of_code: 500,
complexity_sum: 100,
god_object_score: 85.0,
recommended_splits: vec![],
confidence: GodObjectConfidence::Probable,
responsibilities: vec!["data".to_string()],
responsibility_method_counts: HashMap::new(),
purity_distribution: None,
module_structure: None,
detection_type: DetectionType::GodFile,
struct_name: None,
struct_line: None,
struct_location: None,
visibility_breakdown: None,
domain_count: 1,
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,
}),
tier: None,
function_context: None,
context_confidence: None,
contextual_recommendation: None,
pattern_analysis: None,
file_context: None,
context_multiplier: None,
context_type: None,
language_specific: None,
detected_pattern: None,
contextual_risk: None,
file_line_count: None,
responsibility_category: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
context_suggestion: None,
}
}
#[test]
fn test_god_object_with_zero_score_is_filtered() {
let call_graph = CallGraph::new();
let mut analysis = UnifiedAnalysis::new(call_graph);
let zero_score_god_object = create_god_object_item(0.0);
analysis.add_item(zero_score_god_object);
assert_eq!(
analysis.items.len(),
0,
"God object with score 0.0 should be filtered out (score 0.0 = not debt)"
);
}
#[test]
fn test_god_object_with_positive_score_is_included() {
let call_graph = CallGraph::new();
let mut analysis = UnifiedAnalysis::new(call_graph);
let god_object = create_god_object_item(50.0);
analysis.add_item(god_object);
assert_eq!(
analysis.items.len(),
1,
"God object with positive score should be included"
);
}
}