use std::collections::HashMap;
use crate::metrics::dimensions::{Distance, Subdomain, Visibility};
use crate::metrics::project::ProjectMetrics;
use super::action::RefactoringAction;
use super::coupling::is_entrypoint_module;
use super::issue::CouplingIssue;
use super::issue_type::IssueType;
use super::labels::extract_type_name;
use super::score::IssueThresholds;
use super::severity::Severity;
use super::subdomain::build_target_subdomain_map;
pub(crate) fn analyze_module_coupling(
metrics: &ProjectMetrics,
thresholds: &IssueThresholds,
) -> Vec<CouplingIssue> {
let mut issues = Vec::new();
let target_subdomains = build_target_subdomain_map(metrics);
let mut efferent: HashMap<&str, usize> = HashMap::new();
let mut afferent: HashMap<&str, usize> = HashMap::new();
for coupling in &metrics.couplings {
if coupling.distance == Distance::DifferentCrate {
continue;
}
*efferent.entry(&coupling.source).or_insert(0) += 1;
*afferent.entry(&coupling.target).or_insert(0) += 1;
}
for (module, count) in &efferent {
if *count > thresholds.max_dependencies {
let entrypoint = is_entrypoint_module(module);
let severity = if entrypoint {
Severity::Low
} else if *count > thresholds.max_dependencies * 2 {
Severity::High
} else {
Severity::Medium
};
let description = if entrypoint {
format!(
"Entrypoint {} wires {} components — expected for a binary entrypoint, not a defect",
module, count
)
} else {
format!(
"Module {} depends on {} other components (threshold: {})",
module, count, thresholds.max_dependencies
)
};
let refactoring = if entrypoint {
RefactoringAction::General {
action: "No action needed: entrypoints are expected to have wide fan-out"
.to_string(),
}
} else {
RefactoringAction::SplitModule {
suggested_modules: vec![
format!("{}_core", module),
format!("{}_integration", module),
],
}
};
issues.push(CouplingIssue {
issue_type: IssueType::HighEfferentCoupling,
severity,
source: module.to_string(),
target: format!("{} dependencies", count),
description,
refactoring,
balance_score: 1.0
- (*count as f64 / (thresholds.max_dependencies * 3) as f64).min(1.0),
});
}
}
for (module, count) in &afferent {
if *count > thresholds.max_dependents {
let short = module.rsplit("::").next().unwrap_or(module);
let subdomain = target_subdomains.get(short).copied().flatten();
let base_severity = if *count > thresholds.max_dependents * 2 {
Severity::High
} else {
Severity::Medium
};
let (severity, description, refactoring) = match subdomain {
Some(Subdomain::Supporting) | Some(Subdomain::Generic) => (
Severity::Low,
format!(
"Module {} is a stable central abstraction with {} dependents — acceptable; only risky if it becomes volatile",
module, count
),
RefactoringAction::General {
action: "No action needed while this module stays stable".to_string(),
},
),
Some(Subdomain::Core) => (
base_severity,
format!(
"Core module {} is depended on by {} components AND genuinely evolves — changes cascade widely; stabilize its public contract",
module, count
),
RefactoringAction::IntroduceTrait {
suggested_name: format!("{}Interface", extract_type_name(module)),
methods: vec!["// Define a stable public contract".to_string()],
},
),
None => (
base_severity,
format!(
"Module {} is depended on by {} other components (threshold: {})",
module, count, thresholds.max_dependents
),
RefactoringAction::IntroduceTrait {
suggested_name: format!("{}Interface", extract_type_name(module)),
methods: vec!["// Define stable public API".to_string()],
},
),
};
issues.push(CouplingIssue {
issue_type: IssueType::HighAfferentCoupling,
severity,
source: format!("{} dependents", count),
target: module.to_string(),
description,
refactoring,
balance_score: 1.0
- (*count as f64 / (thresholds.max_dependents * 3) as f64).min(1.0),
});
}
}
issues
}
pub(crate) fn analyze_rust_patterns(
metrics: &ProjectMetrics,
thresholds: &IssueThresholds,
) -> Vec<CouplingIssue> {
let mut issues = Vec::new();
for (module_name, module) in &metrics.modules {
let func_count = if thresholds.exclude_tests {
module
.function_count()
.saturating_sub(module.test_function_count)
} else {
module.function_count()
};
let type_count = module.type_definitions.len();
let impl_count = module.trait_impl_count + module.inherent_impl_count;
let is_god_module = func_count > thresholds.max_functions
|| type_count > thresholds.max_types
|| impl_count > thresholds.max_impls;
if is_god_module {
issues.push(CouplingIssue {
issue_type: IssueType::GodModule,
severity: if func_count > thresholds.max_functions * 2
|| type_count > thresholds.max_types * 2
{
Severity::High
} else {
Severity::Medium
},
source: module_name.clone(),
target: format!(
"{} functions, {} types, {} impls",
func_count, type_count, impl_count
),
description: format!(
"Module {} has too many responsibilities (functions: {}/{}, types: {}/{}, impls: {}/{})",
module_name,
func_count, thresholds.max_functions,
type_count, thresholds.max_types,
impl_count, thresholds.max_impls,
),
refactoring: RefactoringAction::SplitModule {
suggested_modules: vec![
format!("{}_core", module_name),
format!("{}_helpers", module_name),
],
},
balance_score: 0.5,
});
}
for type_def in module.type_definitions.values() {
if type_def.public_field_count > 0
&& !type_def.is_trait
&& type_def.visibility == Visibility::Public
{
issues.push(CouplingIssue {
issue_type: IssueType::PublicFieldExposure,
severity: Severity::Low,
source: format!("{}::{}", module_name, type_def.name),
target: format!("{} public fields", type_def.public_field_count),
description: format!(
"Type {} has {} public field(s). Consider using getter methods.",
type_def.name, type_def.public_field_count
),
refactoring: RefactoringAction::AddGetters {
fields: vec!["// Add getter methods".to_string()],
},
balance_score: 0.7,
});
}
}
for func_def in module.function_definitions.values() {
if func_def.primitive_param_count >= thresholds.min_primitive_params
&& func_def.param_count >= thresholds.min_primitive_params
{
let ratio = func_def.primitive_param_count as f64 / func_def.param_count as f64;
if ratio >= 0.6 {
issues.push(CouplingIssue {
issue_type: IssueType::PrimitiveObsession,
severity: Severity::Low,
source: format!("{}::{}", module_name, func_def.name),
target: format!(
"{}/{} primitive params",
func_def.primitive_param_count, func_def.param_count
),
description: format!(
"Function {} has {} primitive parameters. Consider newtype pattern.",
func_def.name, func_def.primitive_param_count
),
refactoring: RefactoringAction::IntroduceNewtype {
suggested_name: format!("{}Params", capitalize_first(&func_def.name)),
wrapped_type: "// Group related parameters".to_string(),
},
balance_score: 0.7,
});
}
}
}
}
issues
}
fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}