1use super::{FileDebtItem, UnifiedAnalysis, UnifiedDebtItem};
7use crate::data_flow::{DataFlowGraph, IoOperation, PurityInfo};
8use crate::priority::call_graph::FunctionId;
9use std::cmp::Ordering;
10
11fn compare_debt_items_by_score(a: &UnifiedDebtItem, b: &UnifiedDebtItem) -> Ordering {
16 b.unified_score
17 .final_score
18 .partial_cmp(&a.unified_score.final_score)
19 .unwrap_or(Ordering::Equal)
20 .then_with(|| a.location.file.cmp(&b.location.file))
21 .then_with(|| a.location.line.cmp(&b.location.line))
22 .then_with(|| a.location.function.cmp(&b.location.function))
23}
24
25fn compare_file_items_by_score(a: &FileDebtItem, b: &FileDebtItem) -> Ordering {
28 b.score
29 .partial_cmp(&a.score)
30 .unwrap_or(Ordering::Equal)
31 .then_with(|| a.metrics.path.cmp(&b.metrics.path))
32}
33
34pub trait UnifiedAnalysisUtils {
36 fn timings(&self) -> Option<&crate::builders::parallel_unified_analysis::AnalysisPhaseTimings>;
38
39 fn add_file_item(&mut self, item: FileDebtItem);
41
42 fn add_item(&mut self, item: UnifiedDebtItem);
44
45 fn sort_by_priority(&mut self);
47
48 fn data_flow_graph(&self) -> &DataFlowGraph;
50
51 fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph;
53
54 fn populate_purity_analysis(&mut self, metrics: &[crate::core::FunctionMetrics]);
56
57 fn add_io_operation(&mut self, func_id: FunctionId, operation: IoOperation);
59
60 fn add_variable_dependencies(
62 &mut self,
63 func_id: FunctionId,
64 variables: std::collections::HashSet<String>,
65 );
66
67 fn apply_file_context_adjustments(
72 &mut self,
73 file_contexts: &std::collections::HashMap<std::path::PathBuf, crate::analysis::FileContext>,
74 );
75}
76
77impl UnifiedAnalysisUtils for UnifiedAnalysis {
78 fn timings(&self) -> Option<&crate::builders::parallel_unified_analysis::AnalysisPhaseTimings> {
79 self.timings.as_ref()
80 }
81
82 fn add_file_item(&mut self, item: FileDebtItem) {
83 let min_score = crate::config::get_minimum_debt_score();
85
86 if item.score <= 0.0 || item.score < min_score {
89 return;
90 }
91
92 let is_duplicate = self
94 .file_items
95 .iter()
96 .any(|existing| existing.metrics.path == item.metrics.path);
97
98 if !is_duplicate {
99 self.file_items.push_back(item);
100 }
101 }
102
103 fn add_item(&mut self, item: UnifiedDebtItem) {
104 use crate::priority::filter_config::ItemFilterConfig;
105 use crate::priority::filter_predicates::*;
106
107 self.stats.total_items_processed += 1;
108
109 if item.unified_score.final_score <= 0.0 {
112 self.stats.filtered_by_score += 1;
113 return;
114 }
115
116 let is_god_object = item
118 .god_object_indicators
119 .as_ref()
120 .is_some_and(|indicators| indicators.is_god_object);
121
122 if !is_god_object {
123 let config = ItemFilterConfig::from_environment();
125
126 if !meets_score_threshold(&item, config.min_score) {
128 self.stats.filtered_by_score += 1;
129 return;
130 }
131
132 if !meets_risk_threshold(&item, config.min_risk) {
133 self.stats.filtered_by_risk += 1;
134 return;
135 }
136
137 if !meets_complexity_thresholds(&item, config.min_cyclomatic, config.min_cognitive) {
138 self.stats.filtered_by_complexity += 1;
139 return;
140 }
141 }
142
143 if self
145 .items
146 .iter()
147 .any(|existing| is_duplicate_of(&item, existing))
148 {
149 self.stats.filtered_as_duplicate += 1;
150 return;
151 }
152
153 self.items.push_back(item);
155 self.stats.items_added += 1;
156 }
157
158 fn sort_by_priority(&mut self) {
159 self.items.sort_by(compare_debt_items_by_score);
161
162 self.file_items.sort_by(compare_file_items_by_score);
164 }
165
166 fn data_flow_graph(&self) -> &DataFlowGraph {
167 &self.data_flow_graph
168 }
169
170 fn data_flow_graph_mut(&mut self) -> &mut DataFlowGraph {
171 &mut self.data_flow_graph
172 }
173
174 fn populate_purity_analysis(&mut self, metrics: &[crate::core::FunctionMetrics]) {
175 for metric in metrics {
176 let func_id = FunctionId::new(metric.file.clone(), metric.name.clone(), metric.line);
177
178 let purity_info = PurityInfo {
179 is_pure: metric.is_pure.unwrap_or(false),
180 confidence: metric.purity_confidence.unwrap_or(0.0),
181 impurity_reasons: if !metric.is_pure.unwrap_or(false) {
182 vec!["Function may have side effects".to_string()]
183 } else {
184 vec![]
185 },
186 };
187
188 self.data_flow_graph.set_purity_info(func_id, purity_info);
189 }
190 }
191
192 fn add_io_operation(&mut self, func_id: FunctionId, operation: IoOperation) {
193 self.data_flow_graph.add_io_operation(func_id, operation);
194 }
195
196 fn add_variable_dependencies(
197 &mut self,
198 func_id: FunctionId,
199 variables: std::collections::HashSet<String>,
200 ) {
201 self.data_flow_graph
202 .add_variable_dependencies(func_id, variables);
203 }
204
205 fn apply_file_context_adjustments(
206 &mut self,
207 file_contexts: &std::collections::HashMap<std::path::PathBuf, crate::analysis::FileContext>,
208 ) {
209 use crate::priority::scoring::file_context_scoring::apply_context_adjustments;
210
211 self.items = self
213 .items
214 .iter()
215 .map(|item| {
216 if let Some(context) = file_contexts.get(&item.location.file) {
218 let adjusted_score =
220 apply_context_adjustments(item.unified_score.final_score, context);
221
222 let mut adjusted_item = item.clone();
224 adjusted_item.unified_score.final_score = adjusted_score.max(0.0);
225 adjusted_item.file_context = Some(context.clone());
226 adjusted_item
227 } else {
228 item.clone()
230 }
231 })
232 .collect();
233 }
234}
235
236#[cfg(test)]
237mod tests {
238 use super::*;
239 use crate::organization::{
240 DetectionType, GodObjectAnalysis, GodObjectConfidence, SplitAnalysisMethod,
241 };
242 use crate::priority::call_graph::CallGraph;
243 use crate::priority::{
244 ActionableRecommendation, DebtType, FunctionRole, ImpactMetrics, Location, UnifiedScore,
245 };
246 use std::collections::HashMap;
247 use std::path::PathBuf;
248
249 fn create_god_object_item(score: f64) -> UnifiedDebtItem {
250 UnifiedDebtItem {
251 location: Location {
252 file: PathBuf::from("test.rs"),
253 function: "[file-scope]".to_string(),
254 line: 1,
255 },
256 debt_type: DebtType::GodObject {
257 methods: 50,
258 fields: Some(20),
259 responsibilities: 10,
260 god_object_score: 85.0,
261 lines: 500,
262 },
263 unified_score: UnifiedScore {
264 final_score: score,
265 complexity_factor: 0.0,
266 coverage_factor: 0.0,
267 dependency_factor: 0.0,
268 role_multiplier: 1.0,
269 base_score: None,
270 exponential_factor: None,
271 risk_boost: None,
272 pre_adjustment_score: None,
273 adjustment_applied: None,
274 purity_factor: None,
275 refactorability_factor: None,
276 pattern_factor: None,
277 debt_adjustment: None,
278 pre_normalization_score: None,
279 structural_multiplier: Some(1.0),
280 has_coverage_data: false,
281 contextual_risk_multiplier: None,
282 pre_contextual_score: None,
283 debt_type_multiplier: None,
284 },
285 cyclomatic_complexity: 65,
286 cognitive_complexity: 6,
287 function_role: FunctionRole::PureLogic,
288 recommendation: ActionableRecommendation {
289 primary_action: "Split".to_string(),
290 rationale: "God object".to_string(),
291 implementation_steps: vec![],
292 related_items: vec![],
293 steps: None,
294 estimated_effort_hours: None,
295 },
296 expected_impact: ImpactMetrics {
297 coverage_improvement: 0.0,
298 lines_reduction: 0,
299 complexity_reduction: 0.0,
300 risk_reduction: 0.0,
301 },
302 transitive_coverage: None,
303 upstream_dependencies: 0,
304 downstream_dependencies: 0,
305 upstream_callers: vec![],
306 downstream_callees: vec![],
307 upstream_production_callers: vec![],
308 upstream_test_callers: vec![],
309 production_blast_radius: 0,
310 nesting_depth: 0,
311 function_length: 500,
312 is_pure: None,
313 purity_confidence: None,
314 purity_level: None,
315 god_object_indicators: Some(GodObjectAnalysis {
316 is_god_object: true,
317 method_count: 50,
318 weighted_method_count: None,
319 field_count: 20,
320 responsibility_count: 10,
321 lines_of_code: 500,
322 complexity_sum: 100,
323 god_object_score: 85.0,
324 recommended_splits: vec![],
325 confidence: GodObjectConfidence::Probable,
326 responsibilities: vec!["data".to_string()],
327 responsibility_method_counts: HashMap::new(),
328 purity_distribution: None,
329 module_structure: None,
330 detection_type: DetectionType::GodFile,
331 struct_name: None,
332 struct_line: None,
333 struct_location: None,
334 visibility_breakdown: None,
335 domain_count: 1,
336 domain_diversity: 0.0,
337 struct_ratio: 0.0,
338 analysis_method: SplitAnalysisMethod::None,
339 cross_domain_severity: None,
340 domain_diversity_metrics: None,
341 aggregated_entropy: None,
342 aggregated_error_swallowing_count: None,
343 aggregated_error_swallowing_patterns: None,
344 layering_impact: None,
345 anti_pattern_report: None,
346 complexity_metrics: None,
347 trait_method_summary: None,
348 }),
349 tier: None,
350 function_context: None,
351 context_confidence: None,
352 contextual_recommendation: None,
353 pattern_analysis: None,
354 file_context: None,
355 context_multiplier: None,
356 context_type: None,
357 language_specific: None,
358 detected_pattern: None,
359 contextual_risk: None,
360 file_line_count: None,
361 responsibility_category: None,
362 error_swallowing_count: None,
363 error_swallowing_patterns: None,
364 entropy_analysis: None,
365 context_suggestion: None,
366 }
367 }
368
369 #[test]
370 fn test_god_object_with_zero_score_is_filtered() {
371 let call_graph = CallGraph::new();
374 let mut analysis = UnifiedAnalysis::new(call_graph);
375
376 let zero_score_god_object = create_god_object_item(0.0);
378
379 analysis.add_item(zero_score_god_object);
380
381 assert_eq!(
383 analysis.items.len(),
384 0,
385 "God object with score 0.0 should be filtered out (score 0.0 = not debt)"
386 );
387 }
388
389 #[test]
390 fn test_god_object_with_positive_score_is_included() {
391 let call_graph = CallGraph::new();
393 let mut analysis = UnifiedAnalysis::new(call_graph);
394
395 let god_object = create_god_object_item(50.0);
397
398 analysis.add_item(god_object);
399
400 assert_eq!(
402 analysis.items.len(),
403 1,
404 "God object with positive score should be included"
405 );
406 }
407}