use crate::analysis::FileContext;
use crate::analyzers::file_analyzer::UnifiedFileAnalyzer;
use crate::analyzers::FileAnalyzer;
use crate::core::{FunctionMetrics, Language};
use crate::priority::file_metrics::{FileDebtItem, FileDebtMetrics};
use crate::risk::lcov::LcovData;
use std::collections::HashMap;
use std::path::{Path, PathBuf};
pub fn group_functions_by_file(
metrics: &[FunctionMetrics],
) -> HashMap<PathBuf, Vec<FunctionMetrics>> {
let mut files_map = HashMap::new();
for metric in metrics {
files_map
.entry(metric.file.clone())
.or_insert_with(Vec::new)
.push(metric.clone());
}
files_map
}
pub fn aggregate_file_metrics(
functions: &[FunctionMetrics],
coverage_data: Option<&LcovData>,
) -> FileDebtMetrics {
let file_analyzer = UnifiedFileAnalyzer::new(coverage_data.cloned());
file_analyzer.aggregate_functions(functions)
}
pub fn calculate_uncovered_lines(coverage_percent: f64, line_count: usize) -> usize {
((1.0 - coverage_percent) * line_count as f64) as usize
}
pub fn should_include_file(score: f64) -> bool {
score > 50.0
}
pub fn detect_file_context(file_path: &Path, functions: &[FunctionMetrics]) -> FileContext {
let language = Language::from_path(file_path);
let detector = crate::analysis::FileContextDetector::new(language);
detector.detect(file_path, functions)
}
pub fn create_file_debt_item(
file_metrics: FileDebtMetrics,
file_context: Option<&FileContext>,
) -> FileDebtItem {
FileDebtItem::from_metrics(file_metrics, file_context)
}
pub fn enhance_metrics_with_line_count(
mut file_metrics: FileDebtMetrics,
actual_line_count: usize,
) -> FileDebtMetrics {
file_metrics.total_lines = actual_line_count;
file_metrics.uncovered_lines =
calculate_uncovered_lines(file_metrics.coverage_percent, actual_line_count);
file_metrics
}
#[derive(Debug, Clone)]
pub struct ProcessedFileData {
pub file_path: PathBuf,
pub file_metrics: FileDebtMetrics,
pub god_analysis: Option<crate::organization::GodObjectAnalysis>,
pub file_context: FileContext,
pub raw_functions: Vec<FunctionMetrics>,
pub project_root: PathBuf,
}
pub fn process_file_metrics(
file_path: PathBuf,
functions: Vec<FunctionMetrics>,
file_content: Option<&str>,
coverage_data: Option<&LcovData>,
no_god_object: bool,
project_root: &Path,
) -> ProcessedFileData {
let file_analyzer = UnifiedFileAnalyzer::new(coverage_data.cloned());
let mut file_metrics = file_analyzer.aggregate_functions(&functions);
if let Some(content) = file_content {
let actual_line_count = content.lines().count();
file_metrics = enhance_metrics_with_line_count(file_metrics, actual_line_count);
if !no_god_object {
file_metrics.god_object_analysis =
detect_god_object_from_content(&file_analyzer, &file_path, content, &file_metrics);
}
}
let file_context = detect_file_context(&file_path, &functions);
let god_analysis = file_metrics.god_object_analysis.clone();
ProcessedFileData {
file_path,
file_metrics,
god_analysis,
file_context,
raw_functions: functions,
project_root: project_root.to_path_buf(),
}
}
fn detect_god_object_from_content(
file_analyzer: &UnifiedFileAnalyzer,
file_path: &Path,
content: &str,
file_metrics: &FileDebtMetrics,
) -> Option<crate::organization::GodObjectAnalysis> {
let actual_line_count = content.lines().count();
let mut god_analysis = file_analyzer
.analyze_file(file_path, content)
.ok()
.and_then(|m| m.god_object_analysis)
.or_else(|| file_metrics.god_object_analysis.clone());
if actual_line_count > 2000 || file_metrics.function_count > 50 {
if let Some(ref mut analysis) = god_analysis {
analysis.is_god_object = true;
if analysis.god_object_score == 0.0 {
analysis.god_object_score =
((file_metrics.function_count as f64 / 50.0).min(2.0)) * 100.0;
}
} else {
god_analysis = Some(crate::organization::GodObjectAnalysis {
is_god_object: true,
method_count: file_metrics.function_count,
weighted_method_count: None,
field_count: 0,
responsibility_count: 0,
lines_of_code: actual_line_count,
complexity_sum: file_metrics.total_complexity,
god_object_score: ((file_metrics.function_count as f64 / 50.0).min(2.0)) * 100.0,
recommended_splits: Vec::new(),
confidence: crate::organization::GodObjectConfidence::Probable,
responsibilities: Vec::new(),
responsibility_method_counts: std::collections::HashMap::new(),
purity_distribution: None,
module_structure: None,
detection_type: crate::organization::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: crate::organization::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, });
}
}
god_analysis
}
#[cfg(test)]
mod tests {
use super::*;
fn create_test_metric(file: &str, name: &str) -> FunctionMetrics {
FunctionMetrics {
name: name.to_string(),
file: PathBuf::from(file),
line: 1,
length: 10,
cyclomatic: 5,
cognitive: 3,
nesting: 0,
is_test: false,
visibility: None,
is_trait_method: false,
in_test_module: false,
entropy_score: None,
is_pure: None,
purity_confidence: None,
purity_reason: None,
call_dependencies: None,
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
}
}
#[test]
fn test_group_functions_by_file() {
let metrics = vec![
create_test_metric("a.rs", "foo"),
create_test_metric("b.rs", "bar"),
create_test_metric("a.rs", "baz"),
];
let grouped = group_functions_by_file(&metrics);
assert_eq!(grouped.len(), 2);
assert_eq!(grouped.get(&PathBuf::from("a.rs")).unwrap().len(), 2);
assert_eq!(grouped.get(&PathBuf::from("b.rs")).unwrap().len(), 1);
}
#[test]
fn test_calculate_uncovered_lines() {
let uncovered = calculate_uncovered_lines(0.8, 100);
assert!(uncovered == 19 || uncovered == 20); assert_eq!(calculate_uncovered_lines(0.0, 100), 100);
assert_eq!(calculate_uncovered_lines(1.0, 100), 0);
}
#[test]
fn test_should_include_file() {
assert!(should_include_file(51.0));
assert!(!should_include_file(50.0));
assert!(!should_include_file(49.0));
}
}