use super::trait_pattern_analyzer::{TraitPatternAnalyzer, TraitPatternMetrics};
use serde::{Deserialize, Serialize};
use std::path::Path;
use syn;
#[derive(Debug, Clone)]
pub struct BoilerplateDetector {
pub enabled: bool,
pub min_impl_blocks: usize,
pub method_uniformity_threshold: f64,
pub max_avg_complexity: f64,
pub confidence_threshold: f64,
}
impl Default for BoilerplateDetector {
fn default() -> Self {
Self {
enabled: true,
min_impl_blocks: 20,
method_uniformity_threshold: 0.7,
max_avg_complexity: 2.0,
confidence_threshold: 0.7,
}
}
}
impl BoilerplateDetector {
pub fn from_config(config: &BoilerplateDetectionConfig) -> Self {
Self {
enabled: config.enabled,
min_impl_blocks: config.min_impl_blocks,
method_uniformity_threshold: config.method_uniformity_threshold,
max_avg_complexity: config.max_avg_complexity,
confidence_threshold: config.confidence_threshold,
}
}
pub fn detect(&self, path: &Path, ast: &syn::File) -> BoilerplateAnalysis {
if !self.enabled {
return BoilerplateAnalysis {
is_boilerplate: false,
confidence: 0.0,
pattern_type: None,
signals: vec![],
recommendation: String::new(),
};
}
let trait_metrics = TraitPatternAnalyzer::analyze_file(ast);
if trait_metrics.impl_block_count >= self.min_impl_blocks {
let confidence = self.calculate_trait_boilerplate_confidence(&trait_metrics);
if confidence >= self.confidence_threshold {
let signals = self.extract_detection_signals(&trait_metrics);
let pattern = self.classify_pattern(&trait_metrics);
let recommendation =
super::macro_recommendations::MacroRecommendationEngine::generate_recommendation(
&pattern,
path,
);
return BoilerplateAnalysis {
is_boilerplate: true,
confidence,
pattern_type: Some(pattern),
signals,
recommendation,
};
}
}
BoilerplateAnalysis {
is_boilerplate: false,
confidence: 0.0,
pattern_type: None,
signals: vec![],
recommendation: String::new(),
}
}
fn calculate_trait_boilerplate_confidence(&self, metrics: &TraitPatternMetrics) -> f64 {
let mut score = 0.0;
let max_score = 100.0;
if metrics.impl_block_count >= self.min_impl_blocks {
let normalized = (metrics.impl_block_count as f64 / 100.0).min(1.0);
score += 30.0 * normalized;
}
if metrics.method_uniformity >= self.method_uniformity_threshold {
score += 25.0 * metrics.method_uniformity;
}
if metrics.avg_method_complexity < self.max_avg_complexity {
let inverse_complexity =
1.0 - (metrics.avg_method_complexity / self.max_avg_complexity);
score += 20.0 * inverse_complexity;
}
if metrics.complexity_variance < 2.0 {
let normalized = 1.0 - (metrics.complexity_variance / 10.0).min(1.0);
score += 15.0 * normalized;
}
if let Some((_, count)) = &metrics.most_common_trait {
let ratio = *count as f64 / metrics.impl_block_count as f64;
if ratio > 0.8 {
score += 10.0 * ratio;
}
}
(score / max_score).min(1.0)
}
fn extract_detection_signals(&self, metrics: &TraitPatternMetrics) -> Vec<DetectionSignal> {
let mut signals = Vec::new();
if metrics.impl_block_count >= self.min_impl_blocks {
signals.push(DetectionSignal::HighImplCount(metrics.impl_block_count));
}
if metrics.method_uniformity >= self.method_uniformity_threshold {
signals.push(DetectionSignal::HighMethodUniformity(
metrics.method_uniformity,
));
}
if metrics.avg_method_complexity < self.max_avg_complexity {
signals.push(DetectionSignal::LowAvgComplexity(
metrics.avg_method_complexity,
));
}
if metrics.complexity_variance < 2.0 {
signals.push(DetectionSignal::LowComplexityVariance(
metrics.complexity_variance,
));
}
signals
}
fn classify_pattern(&self, metrics: &TraitPatternMetrics) -> BoilerplatePattern {
BoilerplatePattern::TraitImplementation {
trait_name: metrics
.most_common_trait
.as_ref()
.map(|(name, _)| name.clone())
.unwrap_or_else(|| "Unknown".to_string()),
impl_count: metrics.impl_block_count,
shared_methods: metrics
.shared_methods
.iter()
.map(|(name, _)| name.clone())
.collect(),
method_uniformity: metrics.method_uniformity,
}
}
}
#[derive(Debug, Clone)]
pub struct BoilerplateAnalysis {
pub is_boilerplate: bool,
pub confidence: f64,
pub pattern_type: Option<BoilerplatePattern>,
pub signals: Vec<DetectionSignal>,
pub recommendation: String,
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum BoilerplatePattern {
TraitImplementation {
trait_name: String,
impl_count: usize,
shared_methods: Vec<String>,
method_uniformity: f64,
},
BuilderPattern { builder_count: usize },
TestBoilerplate {
test_count: usize,
shared_structure: String,
},
}
#[derive(Debug, Clone, PartialEq, Serialize, Deserialize)]
pub enum DetectionSignal {
HighImplCount(usize),
HighMethodUniformity(f64),
LowAvgComplexity(f64),
HighStructDensity(usize),
LowComplexityVariance(f64),
}
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
pub struct BoilerplateDetectionConfig {
#[serde(default = "default_enabled")]
pub enabled: bool,
#[serde(default = "default_min_impl_blocks")]
pub min_impl_blocks: usize,
#[serde(default = "default_method_uniformity_threshold")]
pub method_uniformity_threshold: f64,
#[serde(default = "default_max_avg_complexity")]
pub max_avg_complexity: f64,
#[serde(default = "default_confidence_threshold")]
pub confidence_threshold: f64,
#[serde(default = "default_detect_trait_impls")]
pub detect_trait_impls: bool,
#[serde(default = "default_detect_builders")]
pub detect_builders: bool,
#[serde(default = "default_detect_test_boilerplate")]
pub detect_test_boilerplate: bool,
}
fn default_enabled() -> bool {
true
}
fn default_min_impl_blocks() -> usize {
20
}
fn default_method_uniformity_threshold() -> f64 {
0.7
}
fn default_max_avg_complexity() -> f64 {
2.0
}
fn default_confidence_threshold() -> f64 {
0.7
}
fn default_detect_trait_impls() -> bool {
true
}
fn default_detect_builders() -> bool {
true
}
fn default_detect_test_boilerplate() -> bool {
true
}
impl Default for BoilerplateDetectionConfig {
fn default() -> Self {
Self {
enabled: true,
min_impl_blocks: 20,
method_uniformity_threshold: 0.7,
max_avg_complexity: 2.0,
confidence_threshold: 0.7,
detect_trait_impls: true,
detect_builders: true,
detect_test_boilerplate: true,
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_default_detector_thresholds() {
let detector = BoilerplateDetector::default();
assert_eq!(detector.min_impl_blocks, 20);
assert_eq!(detector.method_uniformity_threshold, 0.7);
assert_eq!(detector.max_avg_complexity, 2.0);
assert_eq!(detector.confidence_threshold, 0.7);
}
#[test]
fn test_from_config() {
let config = BoilerplateDetectionConfig {
min_impl_blocks: 50,
confidence_threshold: 0.9,
..Default::default()
};
let detector = BoilerplateDetector::from_config(&config);
assert_eq!(detector.min_impl_blocks, 50);
assert_eq!(detector.confidence_threshold, 0.9);
}
#[test]
fn test_calculate_confidence_high_score() {
let detector = BoilerplateDetector::default();
let metrics = TraitPatternMetrics {
impl_block_count: 100,
unique_traits: std::collections::HashSet::from(["Flag".to_string()]),
most_common_trait: Some(("Flag".to_string(), 100)),
method_uniformity: 0.95,
shared_methods: vec![
("name_long".to_string(), 1.0),
("is_switch".to_string(), 1.0),
],
avg_method_complexity: 1.2,
complexity_variance: 0.5,
avg_method_lines: 3.0,
};
let confidence = detector.calculate_trait_boilerplate_confidence(&metrics);
assert!(
confidence > 0.85,
"Confidence should be high for clear boilerplate"
);
}
#[test]
fn test_calculate_confidence_low_score() {
let detector = BoilerplateDetector::default();
let metrics = TraitPatternMetrics {
impl_block_count: 5, unique_traits: std::collections::HashSet::from(["Trait1".to_string()]),
most_common_trait: Some(("Trait1".to_string(), 5)),
method_uniformity: 0.4, shared_methods: vec![],
avg_method_complexity: 8.0, complexity_variance: 5.0, avg_method_lines: 15.0,
};
let confidence = detector.calculate_trait_boilerplate_confidence(&metrics);
assert!(
confidence < 0.5,
"Confidence should be low for non-boilerplate"
);
}
}