use crate::metrics::coupling::CouplingMetrics;
use crate::metrics::dimensions::{Distance, IntegrationStrength};
use crate::volatility::Volatility;
use super::action::RefactoringAction;
use super::issue::CouplingIssue;
use super::issue_type::IssueType;
use super::labels::extract_type_name;
use super::score::{BalanceScore, IssueThresholds};
use super::severity::Severity;
pub(crate) fn is_entrypoint_module(name: &str) -> bool {
name.rsplit("::").next() == Some("main")
}
pub(crate) fn is_crate_root_facade(name: &str) -> bool {
match name.split_once("::") {
Some((krate, rest)) => !rest.contains("::") && rest == krate.replace('-', "_"),
None => false,
}
}
pub fn identify_issues(coupling: &CouplingMetrics) -> Vec<CouplingIssue> {
identify_issues_with_thresholds(coupling, &IssueThresholds::default())
}
pub fn identify_issues_with_thresholds(
coupling: &CouplingMetrics,
_thresholds: &IssueThresholds,
) -> Vec<CouplingIssue> {
let mut issues = Vec::new();
if coupling.distance == Distance::DifferentCrate {
return issues;
}
let balance = BalanceScore::calculate(coupling);
let target_is_facade = is_crate_root_facade(&coupling.target);
if coupling.strength == IntegrationStrength::Intrusive
&& coupling.distance == Distance::DifferentModule
&& coupling.volatility != Volatility::High
&& !target_is_facade
{
let (severity, description) = if coupling.volatility == Volatility::Low {
(
Severity::Low,
format!(
"Acceptable: intrusive coupling to stable {} across a module boundary (low volatility neutralizes the distance)",
coupling.target
),
)
} else {
(
Severity::Medium,
format!(
"Intrusive coupling to {} across a module boundary",
coupling.target
),
)
};
issues.push(CouplingIssue {
issue_type: IssueType::GlobalComplexity,
severity,
source: coupling.source.clone(),
target: coupling.target.clone(),
description,
refactoring: RefactoringAction::IntroduceTrait {
suggested_name: format!("{}Trait", extract_type_name(&coupling.target)),
methods: vec!["// Extract required methods".to_string()],
},
balance_score: balance.score,
});
}
if coupling.strength == IntegrationStrength::Intrusive
&& coupling.distance == Distance::DifferentModule
&& coupling.volatility == Volatility::High
&& !target_is_facade
{
issues.push(CouplingIssue {
issue_type: IssueType::CascadingChangeRisk,
severity: Severity::High,
source: coupling.source.clone(),
target: coupling.target.clone(),
description: format!(
"Intrusive coupling to frequently-changed component {}",
coupling.target,
),
refactoring: RefactoringAction::StabilizeInterface {
interface_name: format!("{}Interface", extract_type_name(&coupling.target)),
},
balance_score: balance.score,
});
}
if coupling.strength == IntegrationStrength::Intrusive
&& coupling.distance == Distance::DifferentModule
&& balance.score < 0.5
&& !target_is_facade
{
if !issues
.iter()
.any(|i| i.issue_type == IssueType::GlobalComplexity)
{
issues.push(CouplingIssue {
issue_type: IssueType::InappropriateIntimacy,
severity: Severity::Medium,
source: coupling.source.clone(),
target: coupling.target.clone(),
description: format!(
"Direct internal access to {} across module boundary",
coupling.target,
),
refactoring: RefactoringAction::IntroduceTrait {
suggested_name: format!("{}Api", extract_type_name(&coupling.target)),
methods: vec!["// Expose only necessary operations".to_string()],
},
balance_score: balance.score,
});
}
}
issues
}