use super::TraitMetrics;
use super::evaluation::BrainTraitDisposition;
use crate::decomposition_advice::{
DecompositionContext, DecompositionSuggestion, SubjectKind, format_diagnostic_note,
};
#[cfg(test)]
#[path = "diagnostic_tests.rs"]
mod tests;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct BrainTraitDiagnostic {
trait_name: String,
disposition: BrainTraitDisposition,
required_method_count: usize,
default_method_count: usize,
default_method_cc_sum: usize,
total_item_count: usize,
implementor_burden: usize,
}
impl BrainTraitDiagnostic {
#[must_use]
pub fn new(metrics: &TraitMetrics, disposition: BrainTraitDisposition) -> Self {
Self {
trait_name: metrics.trait_name().to_owned(),
disposition,
required_method_count: metrics.required_method_count(),
default_method_count: metrics.default_method_count(),
default_method_cc_sum: metrics.default_method_cc_sum(),
total_item_count: metrics.total_item_count(),
implementor_burden: metrics.implementor_burden(),
}
}
#[must_use]
pub fn trait_name(&self) -> &str {
&self.trait_name
}
#[must_use]
pub fn disposition(&self) -> BrainTraitDisposition {
self.disposition
}
#[must_use]
pub fn required_method_count(&self) -> usize {
self.required_method_count
}
#[must_use]
pub fn default_method_count(&self) -> usize {
self.default_method_count
}
#[must_use]
pub fn total_method_count(&self) -> usize {
self.required_method_count + self.default_method_count
}
#[must_use]
pub fn default_method_cc_sum(&self) -> usize {
self.default_method_cc_sum
}
#[must_use]
pub fn total_item_count(&self) -> usize {
self.total_item_count
}
#[must_use]
pub fn implementor_burden(&self) -> usize {
self.implementor_burden
}
}
#[must_use]
pub fn format_primary_message(diagnostic: &BrainTraitDiagnostic) -> String {
let name = diagnostic.trait_name();
let total = diagnostic.total_method_count();
let req = diagnostic.required_method_count();
let def = diagnostic.default_method_count();
let cc = diagnostic.default_method_cc_sum();
if cc > 0 {
format!(
"`{name}` has {total} methods ({req} required, \
{def} default) with default method complexity CC={cc}."
)
} else {
format!("`{name}` has {total} methods ({req} required, {def} default).")
}
}
#[must_use]
pub fn format_note(diagnostic: &BrainTraitDiagnostic) -> String {
let mut note =
String::from("Total method count measures interface size and implementation surface area.");
if diagnostic.default_method_cc_sum() > 0 {
note.push_str(
" Default method CC sum measures complexity hidden behind \
the trait's default implementations.",
);
}
if diagnostic.required_method_count() > 0 {
note.push_str(
" Implementor burden indicates how many methods each \
implementor must provide.",
);
}
note
}
#[must_use]
pub fn format_decomposition_note(
diagnostic: &BrainTraitDiagnostic,
suggestions: &[DecompositionSuggestion],
) -> Option<String> {
let context = DecompositionContext::new(diagnostic.trait_name(), SubjectKind::Trait);
format_diagnostic_note(&context, suggestions)
}
#[must_use]
pub fn format_help(diagnostic: &BrainTraitDiagnostic) -> String {
let mut parts: Vec<&str> = Vec::new();
if diagnostic.total_method_count() > 0 {
parts.push("splitting the trait into focused sub-traits");
}
if diagnostic.default_method_cc_sum() > 0 {
parts.push(
"extracting complex default method bodies into free \
functions or helper traits",
);
}
if diagnostic.required_method_count() > 0 {
parts.push("providing more default implementations to reduce implementor burden");
}
if parts.is_empty() {
return String::from(
"Consider splitting the trait into smaller, focused \
sub-traits to reduce complexity.",
);
}
format!("Consider {}.", parts.join(", "))
}