use crate::core::{FunctionMetrics, PurityLevel};
use crate::data_flow::DataFlowGraph;
use crate::organization::GodObjectAnalysis;
use crate::priority::{
call_graph::{CallGraph, FunctionId},
coverage_propagation::TransitiveCoverage,
debt_aggregator::{DebtAggregator, FunctionId as AggregatorFunctionId},
scoring::calculation::{
calculate_base_score_no_coverage, calculate_base_score_with_coverage_multiplier,
calculate_complexity_factor, calculate_coverage_factor, calculate_coverage_multiplier,
calculate_dependency_factor,
},
scoring::debt_item::{determine_visibility, is_dead_code},
semantic_classifier::{classify_function_role, FunctionRole},
ActionableRecommendation, DebtType, FunctionAnalysis, ImpactMetrics,
};
use crate::risk::evidence_calculator::EvidenceBasedRiskCalculator;
use crate::risk::lcov::LcovData;
use serde::{Deserialize, Serialize};
use std::collections::HashSet;
use std::path::PathBuf;
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct DebtAdjustmentDetails {
pub total: f64,
pub testing: f64,
pub resource: f64,
pub duplication: f64,
}
impl DebtAdjustmentDetails {
pub fn new(testing: f64, resource: f64, duplication: f64) -> Self {
Self {
total: testing + resource + duplication,
testing,
resource,
duplication,
}
}
pub fn zero() -> Self {
Self {
total: 0.0,
testing: 0.0,
resource: 0.0,
duplication: 0.0,
}
}
pub fn is_significant(&self) -> bool {
self.total.abs() > 0.01
}
}
#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
pub enum PuritySpectrum {
StrictlyPure,
LocallyPure,
IOIsolated,
IOMixed,
Impure,
}
impl PuritySpectrum {
pub fn score_multiplier(&self) -> f64 {
match self {
PuritySpectrum::StrictlyPure => 0.0,
PuritySpectrum::LocallyPure => 0.3,
PuritySpectrum::IOIsolated => 0.6,
PuritySpectrum::IOMixed => 0.9,
PuritySpectrum::Impure => 1.0,
}
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedScore {
pub complexity_factor: f64, pub coverage_factor: f64, pub dependency_factor: f64, pub role_multiplier: f64, pub final_score: f64, #[serde(skip_serializing_if = "Option::is_none")]
pub base_score: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub exponential_factor: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub risk_boost: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub pre_adjustment_score: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub adjustment_applied:
Option<crate::priority::scoring::orchestration_adjustment::ScoreAdjustment>, #[serde(skip_serializing_if = "Option::is_none")]
pub purity_factor: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub refactorability_factor: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub pattern_factor: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub debt_adjustment: Option<DebtAdjustmentDetails>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_normalization_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub structural_multiplier: Option<f64>,
#[serde(default)]
pub has_coverage_data: bool,
#[serde(skip_serializing_if = "Option::is_none")]
pub contextual_risk_multiplier: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub pre_contextual_score: Option<f64>,
#[serde(skip_serializing_if = "Option::is_none")]
pub debt_type_multiplier: Option<f64>,
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct UnifiedDebtItem {
pub location: Location,
pub debt_type: DebtType,
pub unified_score: UnifiedScore,
pub function_role: FunctionRole,
pub recommendation: ActionableRecommendation,
pub expected_impact: ImpactMetrics,
pub transitive_coverage: Option<TransitiveCoverage>,
pub upstream_dependencies: usize,
pub downstream_dependencies: usize,
pub upstream_callers: Vec<String>, pub downstream_callees: Vec<String>, #[serde(default, skip_serializing_if = "Vec::is_empty")]
pub upstream_production_callers: Vec<String>,
#[serde(default, skip_serializing_if = "Vec::is_empty")]
pub upstream_test_callers: Vec<String>,
#[serde(default)]
pub production_blast_radius: usize,
pub nesting_depth: u32,
pub function_length: usize,
pub cyclomatic_complexity: u32,
pub cognitive_complexity: u32,
#[serde(skip_serializing_if = "Option::is_none")]
pub entropy_analysis: Option<crate::complexity::EntropyAnalysis>,
pub is_pure: Option<bool>, pub purity_confidence: Option<f32>, #[serde(skip_serializing_if = "Option::is_none")]
pub purity_level: Option<PurityLevel>, pub god_object_indicators: Option<GodObjectAnalysis>, #[serde(skip)]
pub tier: Option<crate::priority::RecommendationTier>, pub function_context: Option<crate::analysis::FunctionContext>, pub context_confidence: Option<f64>, pub contextual_recommendation: Option<crate::priority::scoring::ContextualRecommendation>, #[serde(skip_serializing_if = "Option::is_none")]
pub pattern_analysis: Option<crate::output::PatternAnalysis>, #[serde(skip_serializing_if = "Option::is_none")]
pub file_context: Option<crate::analysis::FileContext>, #[serde(skip_serializing_if = "Option::is_none")]
pub context_multiplier: Option<f64>, #[serde(skip_serializing_if = "Option::is_none")]
pub context_type: Option<crate::context::FileType>, #[serde(skip_serializing_if = "Option::is_none")]
pub language_specific: Option<crate::core::LanguageSpecificData>, #[serde(skip_serializing_if = "Option::is_none")]
pub detected_pattern: Option<crate::priority::detected_pattern::DetectedPattern>, #[serde(skip_serializing_if = "Option::is_none")]
pub contextual_risk: Option<crate::risk::context::ContextualRisk>, #[serde(skip_serializing_if = "Option::is_none")]
pub file_line_count: Option<usize>,
#[serde(skip_serializing_if = "Option::is_none")]
pub responsibility_category: Option<String>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_swallowing_count: Option<u32>,
#[serde(default, skip_serializing_if = "Option::is_none")]
pub error_swallowing_patterns: Option<Vec<String>>,
#[serde(skip_serializing_if = "Option::is_none")]
pub context_suggestion: Option<crate::priority::context::ContextSuggestion>,
}
impl UnifiedDebtItem {
pub fn with_pattern_analysis(
mut self,
pattern_analysis: crate::output::PatternAnalysis,
) -> Self {
self.pattern_analysis = Some(pattern_analysis);
self
}
}
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Location {
pub file: PathBuf,
pub function: String,
pub line: usize,
}
pub fn calculate_unified_priority(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
organization_issues: Option<f64>,
) -> UnifiedScore {
let has_coverage_data = coverage.is_some();
calculate_unified_priority_with_debt(
func,
call_graph,
coverage,
organization_issues,
None,
has_coverage_data,
)
}
pub fn calculate_unified_score_with_patterns(
func: &FunctionMetrics,
god_object: Option<&GodObjectAnalysis>,
coverage: Option<&LcovData>,
call_graph: &CallGraph,
) -> UnifiedScore {
let has_coverage_data = coverage.is_some();
let base_score = calculate_unified_priority_with_debt(
func,
call_graph,
coverage,
None,
None,
has_coverage_data,
);
let god_object_multiplier = if let Some(go) = god_object {
if go.is_god_object {
3.0 + (go.god_object_score / 50.0)
} else {
1.0
}
} else {
1.0
};
UnifiedScore {
complexity_factor: base_score.complexity_factor * god_object_multiplier,
coverage_factor: base_score.coverage_factor,
dependency_factor: base_score.dependency_factor,
role_multiplier: base_score.role_multiplier,
final_score: (base_score.final_score.max(0.0) * god_object_multiplier),
base_score: base_score.base_score,
exponential_factor: base_score.exponential_factor,
risk_boost: base_score.risk_boost,
pre_adjustment_score: base_score.pre_adjustment_score,
adjustment_applied: base_score.adjustment_applied,
purity_factor: base_score.purity_factor,
refactorability_factor: base_score.refactorability_factor,
pattern_factor: base_score.pattern_factor,
debt_adjustment: base_score.debt_adjustment,
pre_normalization_score: base_score.pre_normalization_score,
structural_multiplier: base_score.structural_multiplier,
has_coverage_data: base_score.has_coverage_data,
contextual_risk_multiplier: base_score.contextual_risk_multiplier,
pre_contextual_score: base_score.pre_contextual_score,
debt_type_multiplier: base_score.debt_type_multiplier,
}
}
pub fn calculate_unified_priority_with_debt(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
_organization_issues: Option<f64>, debt_aggregator: Option<&DebtAggregator>,
has_coverage_data: bool,
) -> UnifiedScore {
let func_id = FunctionId::new(func.file.clone(), func.name.clone(), func.line);
let role = classify_function_role(func, &func_id, call_graph);
calculate_unified_priority_with_role(
func,
&func_id,
call_graph,
coverage,
debt_aggregator,
has_coverage_data,
role,
)
}
pub fn calculate_unified_priority_with_role(
func: &FunctionMetrics,
func_id: &FunctionId,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
debt_aggregator: Option<&DebtAggregator>,
has_coverage_data: bool,
role: FunctionRole,
) -> UnifiedScore {
let is_trivial = is_trivial_function(func, role);
let coverage_pct = get_function_coverage(func, coverage);
let has_coverage = coverage_pct > 0.0;
if should_skip_as_non_debt(is_trivial, has_coverage) {
return UnifiedScore {
complexity_factor: 0.0,
coverage_factor: 0.0,
dependency_factor: 0.0,
role_multiplier: 1.0,
final_score: 0.0,
base_score: Some(0.0),
exponential_factor: Some(1.0),
risk_boost: Some(1.0),
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,
contextual_risk_multiplier: None,
pre_contextual_score: None,
debt_type_multiplier: None,
};
}
let is_orchestrator_candidate = role == FunctionRole::Orchestrator;
let entropy_analysis = crate::priority::scoring::computation::calculate_entropy_analysis(func);
let purity_bonus = calculate_purity_adjustment(func);
let (purity_adjusted_cyclomatic, purity_adjusted_cognitive) =
apply_purity_adjustment(func.cyclomatic, func.cognitive, purity_bonus);
let raw_complexity = normalize_complexity(
purity_adjusted_cyclomatic,
purity_adjusted_cognitive,
entropy_analysis.as_ref(),
is_orchestrator_candidate,
);
let complexity_factor = calculate_complexity_factor(raw_complexity);
let upstream_callers = call_graph.get_callers(func_id);
let production_upstream_count = count_production_callers(&upstream_callers, call_graph);
let dependency_factor = calculate_dependency_factor(production_upstream_count);
let role_multiplier = calculate_role_multiplier(role, raw_complexity);
let coverage_weight = get_role_coverage_weight(role);
let base_score = calculate_base_score(
has_coverage_data,
func.is_test,
coverage_pct,
coverage_weight,
complexity_factor,
dependency_factor,
);
let _coverage_factor = if has_coverage_data {
if func.is_test {
0.1
} else {
calculate_coverage_factor(coverage_pct)
}
} else {
0.0
};
let role_config = crate::config::get_role_multiplier_config();
let clamped_role_multiplier = if role_config.enable_clamping {
role_multiplier.clamp(role_config.clamp_min, role_config.clamp_max)
} else {
role_multiplier
};
let role_adjusted_score = base_score * clamped_role_multiplier;
let structural_multiplier =
calculate_structural_quality_multiplier(func.nesting, func.cyclomatic);
let structure_adjusted_score = role_adjusted_score * structural_multiplier;
let (debt_adjustment, debt_details) =
calculate_debt_adjustment_with_details(func, debt_aggregator);
let debt_adjusted_score = structure_adjusted_score + debt_adjustment;
let floored_score = debt_adjusted_score.max(0.0);
let pre_normalization_score = if debt_adjusted_score < 0.0 {
Some(debt_adjusted_score)
} else {
None
};
let (final_normalized_score, pre_adjustment, adjustment) = apply_orchestration_adjustment(
is_orchestrator_candidate,
floored_score,
func_id,
func,
call_graph,
&role,
);
let debt_adjustment_details = if debt_details.total.abs() > 0.001 {
Some(debt_details)
} else {
None
};
UnifiedScore {
complexity_factor,
coverage_factor: (1.0 - coverage_pct) * 10.0, dependency_factor,
role_multiplier,
final_score: final_normalized_score.max(0.0),
base_score: None, exponential_factor: None, risk_boost: None, pre_adjustment_score: pre_adjustment,
adjustment_applied: adjustment,
purity_factor: None, refactorability_factor: None, pattern_factor: None, debt_adjustment: debt_adjustment_details,
pre_normalization_score,
structural_multiplier: Some(structural_multiplier),
has_coverage_data,
contextual_risk_multiplier: None, pre_contextual_score: None, debt_type_multiplier: None, }
}
fn is_trivial_function(func: &FunctionMetrics, role: FunctionRole) -> bool {
(func.cyclomatic <= 3 && func.cognitive <= 5)
&& (role == FunctionRole::IOWrapper
|| role == FunctionRole::EntryPoint
|| role == FunctionRole::PatternMatch
|| role == FunctionRole::Debug
|| (role == FunctionRole::PureLogic && func.length <= 10))
}
fn get_function_coverage(func: &FunctionMetrics, coverage: Option<&LcovData>) -> f64 {
if func.is_test {
1.0 } else if let Some(cov) = coverage {
cov.get_function_coverage(&func.file, &func.name)
.unwrap_or(0.0)
} else {
0.0 }
}
fn should_skip_as_non_debt(is_trivial: bool, has_coverage: bool) -> bool {
is_trivial && has_coverage
}
fn calculate_purity_adjustment(func: &FunctionMetrics) -> f64 {
if let Some(level) = func.purity_level {
let confidence = func.purity_confidence.unwrap_or(0.0);
return match level {
PurityLevel::StrictlyPure => {
if confidence > 0.8 {
0.70 } else {
0.80 }
}
PurityLevel::LocallyPure => {
if confidence > 0.8 {
0.75 } else {
0.85 }
}
PurityLevel::ReadOnly => 0.90, PurityLevel::Impure => 1.0, };
}
if func.is_pure == Some(true) {
if func.purity_confidence.unwrap_or(0.0) > 0.8 {
0.70
} else {
0.85
}
} else {
1.0 }
}
fn apply_purity_adjustment(cyclomatic: u32, cognitive: u32, adjustment: f64) -> (u32, u32) {
(
(cyclomatic as f64 * adjustment) as u32,
(cognitive as f64 * adjustment) as u32,
)
}
fn calculate_structural_quality_multiplier(nesting: u32, cyclomatic: u32) -> f64 {
if cyclomatic == 0 {
return 1.0;
}
let ratio = nesting as f64 / cyclomatic as f64;
match ratio {
r if r >= 0.6 => 1.5, r if r >= 0.5 => 1.3, r if r >= 0.4 => 1.15, r if r >= 0.2 => 1.0, r if r >= 0.1 => 0.85, _ => 0.7, }
}
fn calculate_role_multiplier(role: FunctionRole, _raw_complexity: f64) -> f64 {
match role {
FunctionRole::EntryPoint => 1.3, FunctionRole::PureLogic => 0.7, FunctionRole::Orchestrator => 0.8, FunctionRole::IOWrapper => 1.2, FunctionRole::PatternMatch => 0.6, FunctionRole::Debug => 0.3, _ => 1.0,
}
}
fn get_role_coverage_weight(role: FunctionRole) -> f64 {
let role_coverage_weights = crate::config::get_role_coverage_weights();
match role {
FunctionRole::EntryPoint => role_coverage_weights.entry_point,
FunctionRole::Orchestrator => role_coverage_weights.orchestrator,
FunctionRole::PureLogic => role_coverage_weights.pure_logic,
FunctionRole::IOWrapper => role_coverage_weights.io_wrapper,
FunctionRole::PatternMatch => role_coverage_weights.pattern_match,
FunctionRole::Debug => role_coverage_weights.pattern_match, _ => role_coverage_weights.unknown,
}
}
fn calculate_base_score(
has_coverage_data: bool,
is_test: bool,
coverage_pct: f64,
coverage_weight: f64,
complexity_factor: f64,
dependency_factor: f64,
) -> f64 {
if has_coverage_data {
let coverage_multiplier = if is_test {
0.0 } else {
let adjusted_coverage_pct = 1.0 - ((1.0 - coverage_pct) * coverage_weight);
calculate_coverage_multiplier(adjusted_coverage_pct)
};
calculate_base_score_with_coverage_multiplier(
coverage_multiplier,
complexity_factor,
dependency_factor,
)
} else {
calculate_base_score_no_coverage(complexity_factor, dependency_factor)
}
}
fn calculate_debt_adjustment_with_details(
func: &FunctionMetrics,
debt_aggregator: Option<&DebtAggregator>,
) -> (f64, DebtAdjustmentDetails) {
if let Some(aggregator) = debt_aggregator {
let agg_func_id =
AggregatorFunctionId::new(func.file.clone(), func.name.clone(), func.line);
let debt_scores = aggregator.calculate_debt_scores(&agg_func_id);
let testing = debt_scores.testing / 50.0;
let resource = debt_scores.resource / 50.0;
let duplication = debt_scores.duplication / 50.0;
let details = DebtAdjustmentDetails::new(testing, resource, duplication);
(details.total, details)
} else {
(0.0, DebtAdjustmentDetails::zero())
}
}
fn apply_orchestration_adjustment(
is_orchestrator: bool,
normalized_score: f64,
func_id: &FunctionId,
func: &FunctionMetrics,
call_graph: &CallGraph,
role: &FunctionRole,
) -> (
f64,
Option<f64>,
Option<crate::priority::scoring::orchestration_adjustment::ScoreAdjustment>,
) {
if !is_orchestrator {
return (normalized_score, None, None);
}
let config = crate::config::get_orchestration_adjustment_config();
if !config.enabled {
return (normalized_score, None, None);
}
let composition_metrics =
crate::priority::scoring::orchestration_adjustment::extract_composition_metrics(
func_id, func, call_graph,
);
let adjustment = crate::priority::scoring::orchestration_adjustment::adjust_score(
&config,
normalized_score,
role,
&composition_metrics,
);
log::debug!(
"Orchestration adjustment applied to {}:{} - Original: {:.2}, Adjusted: {:.2}, Reduction: {:.1}%, Reason: {}",
func.file.display(),
func.name,
adjustment.original_score,
adjustment.adjusted_score,
adjustment.reduction_percent,
adjustment.adjustment_reason
);
(
adjustment.adjusted_score,
Some(normalized_score),
Some(adjustment),
)
}
fn normalize_complexity(
cyclomatic: u32,
cognitive: u32,
entropy_analysis: Option<&crate::complexity::EntropyAnalysis>,
is_orchestrator: bool,
) -> f64 {
let entropy_config = crate::config::get_entropy_config();
let raw_cyclomatic = cyclomatic as f64;
let adjusted_cognitive = if let Some(entropy) = entropy_analysis {
if entropy_config.enabled {
entropy.adjusted_complexity as f64
} else {
cognitive as f64
}
} else {
cognitive as f64
};
let config = crate::config::get_config();
let (cyc_weight, cog_weight) = if let Some(weights_config) = config.complexity_weights.as_ref()
{
(weights_config.cyclomatic, weights_config.cognitive)
} else if is_orchestrator {
(0.25, 0.75)
} else {
(0.4, 0.6)
};
raw_cyclomatic * cyc_weight + adjusted_cognitive * cog_weight
}
fn count_production_callers(callers: &[FunctionId], call_graph: &CallGraph) -> usize {
use crate::priority::caller_classification::{classify_caller, CallerType};
callers
.iter()
.filter(|caller| {
if call_graph.is_test_function(caller) {
return false;
}
classify_caller(&caller.name, Some(call_graph)) == CallerType::Production
})
.count()
}
pub fn create_evidence_based_risk_assessment(
func: &FunctionMetrics,
call_graph: &CallGraph,
coverage: Option<&LcovData>,
) -> crate::risk::evidence::RiskAssessment {
let calculator = EvidenceBasedRiskCalculator::new();
let function_analysis = FunctionAnalysis {
file: func.file.clone(),
function: func.name.clone(),
line: func.line,
function_length: func.length,
cyclomatic_complexity: func.cyclomatic,
cognitive_complexity: func.cognitive,
nesting_depth: func.nesting,
is_test: func.is_test,
visibility: determine_visibility(func),
is_pure: func.is_pure,
purity_confidence: func.purity_confidence,
};
calculator.calculate_risk(&function_analysis, call_graph, coverage)
}
pub fn is_dead_code_with_exclusions(
func: &FunctionMetrics,
call_graph: &CallGraph,
func_id: &FunctionId,
framework_exclusions: &std::collections::HashSet<FunctionId>,
function_pointer_used_functions: Option<&HashSet<FunctionId>>,
) -> bool {
let language = crate::core::Language::from_path(&func.file);
let language_features = crate::config::get_language_features(&language);
if !language_features.detect_dead_code {
return false;
}
if framework_exclusions.contains(func_id) {
return false;
}
is_dead_code(func, call_graph, func_id, function_pointer_used_functions)
}
fn calculate_purity_factor(func_id: &FunctionId, data_flow: &DataFlowGraph) -> f64 {
let purity_info = data_flow.get_purity_info(func_id);
let mutation_info = data_flow.get_mutation_info(func_id);
let io_ops = data_flow.get_io_operations(func_id);
let spectrum = if let Some(purity) = purity_info {
if purity.is_pure && purity.confidence > 0.8 {
if let Some(mutations) = mutation_info {
if mutations.has_mutations {
PuritySpectrum::LocallyPure
} else {
PuritySpectrum::StrictlyPure
}
} else {
PuritySpectrum::StrictlyPure
}
} else if purity.is_pure {
PuritySpectrum::LocallyPure
} else {
classify_io_isolation(io_ops)
}
} else {
PuritySpectrum::Impure
};
spectrum.score_multiplier()
}
fn classify_io_isolation(io_ops: Option<&Vec<crate::data_flow::IoOperation>>) -> PuritySpectrum {
match io_ops {
None => PuritySpectrum::Impure,
Some(ops) if ops.is_empty() => PuritySpectrum::Impure,
Some(ops) => {
let unique_types: HashSet<&String> = ops.iter().map(|op| &op.operation_type).collect();
if unique_types.len() <= 2 && ops.len() <= 3 {
PuritySpectrum::IOIsolated
} else {
PuritySpectrum::IOMixed
}
}
}
}
fn calculate_refactorability_factor(
_func_id: &FunctionId,
_data_flow: &DataFlowGraph,
_config: &crate::config::DataFlowScoringConfig,
) -> f64 {
1.0
}
fn calculate_pattern_factor(func_id: &FunctionId, data_flow: &DataFlowGraph) -> f64 {
let transform_count = count_data_transformations(func_id, data_flow);
let var_deps = data_flow.get_variable_dependencies(func_id);
let dep_count = var_deps.map(|deps| deps.len()).unwrap_or(0);
if transform_count > 0 && dep_count > 0 {
let transform_ratio = transform_count as f64 / dep_count as f64;
if transform_ratio > 0.5 {
0.7
} else if transform_ratio > 0.3 {
0.85
} else {
1.0
}
} else {
1.0
}
}
fn count_data_transformations(func_id: &FunctionId, data_flow: &DataFlowGraph) -> usize {
let call_graph = data_flow.call_graph();
let callees = call_graph.get_callees(func_id);
callees
.iter()
.filter(|callee| data_flow.get_data_transformation(func_id, callee).is_some())
.count()
}
pub fn calculate_unified_priority_with_data_flow(
func: &FunctionMetrics,
call_graph: &CallGraph,
data_flow: &DataFlowGraph,
coverage: Option<&LcovData>,
_organization_issues: Option<f64>, debt_aggregator: Option<&DebtAggregator>,
config: &crate::config::DataFlowScoringConfig,
) -> UnifiedScore {
let func_id = FunctionId::new(func.file.clone(), func.name.clone(), func.line);
let role = classify_function_role(func, &func_id, call_graph);
calculate_unified_priority_with_data_flow_and_role(
func,
&func_id,
call_graph,
data_flow,
coverage,
debt_aggregator,
config,
role,
)
}
#[allow(clippy::too_many_arguments)]
pub fn calculate_unified_priority_with_data_flow_and_role(
func: &FunctionMetrics,
func_id: &FunctionId,
call_graph: &CallGraph,
data_flow: &DataFlowGraph,
coverage: Option<&LcovData>,
debt_aggregator: Option<&DebtAggregator>,
config: &crate::config::DataFlowScoringConfig,
role: FunctionRole,
) -> UnifiedScore {
let has_coverage_data = coverage.is_some();
let mut base_score = calculate_unified_priority_with_role(
func,
func_id,
call_graph,
coverage,
debt_aggregator,
has_coverage_data,
role,
);
if !config.enabled {
return base_score;
}
let purity_factor = calculate_purity_factor(func_id, data_flow);
let refactorability_factor = calculate_refactorability_factor(func_id, data_flow, config);
let pattern_factor = calculate_pattern_factor(func_id, data_flow);
let purity_adjustment = purity_factor * config.purity_weight;
let refactorability_adjustment = refactorability_factor * config.refactorability_weight;
let pattern_adjustment = pattern_factor * config.pattern_weight;
let total_weight = config.purity_weight + config.refactorability_weight + config.pattern_weight;
let combined_adjustment = if total_weight > 0.0 {
(purity_adjustment + refactorability_adjustment + pattern_adjustment) / total_weight
} else {
1.0
};
let adjusted_score = base_score.final_score * combined_adjustment;
base_score.final_score = adjusted_score;
base_score.purity_factor = Some(purity_factor);
base_score.refactorability_factor = Some(refactorability_factor);
base_score.pattern_factor = Some(pattern_factor);
base_score
}
#[cfg(test)]
mod tests;