use crate::detectors::base::{Detector, DetectorConfig};
use crate::graph::GraphStore;
use crate::models::{Finding, Severity};
use anyhow::Result;
use rayon::prelude::*;
use std::collections::{HashMap, VecDeque};
use tracing::{debug, info};
use uuid::Uuid;
pub struct CoreUtilityDetector {
config: DetectorConfig,
high_complexity_threshold: u32,
min_callers_threshold: usize,
}
impl CoreUtilityDetector {
pub fn new() -> Self {
Self {
config: DetectorConfig::new(),
high_complexity_threshold: 20,
min_callers_threshold: 2,
}
}
pub fn with_config(config: DetectorConfig) -> Self {
let high_complexity_threshold = config.get_option_or("high_complexity_threshold", 20);
let min_callers_threshold = config.get_option_or("min_callers_threshold", 2);
Self {
config,
high_complexity_threshold,
min_callers_threshold,
}
}
fn calculate_harmonic(
&self,
adj: &[Vec<usize>],
num_nodes: usize,
normalized: bool,
) -> Vec<f64> {
if num_nodes == 0 {
return vec![];
}
if num_nodes == 1 {
return vec![0.0];
}
let norm_factor = if normalized {
(num_nodes - 1) as f64
} else {
1.0
};
(0..num_nodes)
.into_par_iter()
.map(|source| {
let mut distance: Vec<i32> = vec![-1; num_nodes];
distance[source] = 0;
let mut queue = VecDeque::new();
queue.push_back(source);
let mut score = 0.0;
while let Some(v) = queue.pop_front() {
for &w in &adj[v] {
if distance[w] < 0 {
distance[w] = distance[v] + 1;
queue.push_back(w);
score += 1.0 / distance[w] as f64;
}
}
}
score / norm_factor
})
.collect()
}
fn create_central_coordinator_finding(
&self,
name: &str,
qualified_name: &str,
file_path: &str,
line_number: Option<u32>,
harmonic: f64,
max_harmonic: f64,
complexity: u32,
loc: u32,
caller_count: usize,
callee_count: usize,
) -> Finding {
let percentile = if max_harmonic > 0.0 {
(harmonic / max_harmonic) * 100.0
} else {
0.0
};
let (severity, title) = if complexity > self.high_complexity_threshold {
(
Severity::High,
format!("Central coordinator with high complexity: {}", name),
)
} else {
(Severity::Medium, format!("Central coordinator: {}", name))
};
let mut description = format!(
"Function `{}` has high harmonic centrality \
(score: {:.3}, {:.0}th percentile).\n\n\
**What this means:**\n\
- Can reach most functions in the codebase quickly\n\
- Acts as a coordination point for execution flow\n\
- Changes here have wide-reaching effects\n\n\
**Metrics:**\n\
- Harmonic centrality: {:.3}\n\
- Complexity: {}\n\
- Lines of code: {}\n\
- Callers: {}\n\
- Callees: {}",
name, harmonic, percentile, harmonic, complexity, loc, caller_count, callee_count
);
if complexity > self.high_complexity_threshold {
description.push_str(&format!(
"\n\n**Warning:** High complexity ({}) combined with \
central position creates significant risk.",
complexity
));
}
let suggested_fix = "\
**For central coordinators:**\n\n\
1. **Ensure test coverage**: This function affects many code paths\n\n\
2. **Add monitoring**: Track performance and errors here\n\n\
3. **Review complexity**: Consider splitting if too complex\n\n\
4. **Document thoroughly**: Others need to understand this code\n\n\
5. **Consider patterns**:\n\
- Facade pattern to simplify interface\n\
- Mediator pattern to manage interactions\n\
- Event-driven design to reduce coupling"
.to_string();
let estimated_effort =
if complexity > self.high_complexity_threshold * 2 || caller_count > 20 {
"Large (2-4 hours)"
} else if complexity > self.high_complexity_threshold || caller_count > 10 {
"Medium (1-2 hours)"
} else {
"Small (30-60 minutes)"
};
Finding {
id: Uuid::new_v4().to_string(),
detector: "CoreUtilityDetector".to_string(),
severity,
title,
description,
affected_files: vec![file_path.into()],
line_start: line_number,
line_end: None,
suggested_fix: Some(suggested_fix),
estimated_effort: Some(estimated_effort.to_string()),
category: Some("architecture".to_string()),
cwe_id: None,
why_it_matters: Some(
"Central coordinators are critical nexus points in the codebase. \
They can reach most other code quickly, meaning changes here \
have cascading effects across the system."
.to_string(),
),
..Default::default()
}
}
fn create_isolated_code_finding(
&self,
name: &str,
qualified_name: &str,
file_path: &str,
line_number: Option<u32>,
harmonic: f64,
max_harmonic: f64,
loc: u32,
caller_count: usize,
callee_count: usize,
) -> Option<Finding> {
if loc < 5 {
return None;
}
let percentile = if max_harmonic > 0.0 {
(harmonic / max_harmonic) * 100.0
} else {
0.0
};
let (severity, isolation_level) = if caller_count == 0 && callee_count == 0 {
(Severity::Medium, "completely isolated")
} else if caller_count == 0 {
(Severity::Low, "never called")
} else {
(Severity::Low, "barely connected")
};
let description = format!(
"Function `{}` has very low harmonic centrality \
(score: {:.3}, {:.0}th percentile).\n\n\
**Status:** {}\n\n\
**What this means:**\n\
- Disconnected from most of the codebase\n\
- May be dead code or unused functionality\n\
- Could be misplaced or poorly integrated\n\n\
**Metrics:**\n\
- Harmonic centrality: {:.3}\n\
- Callers: {}\n\
- Callees: {}\n\
- Lines of code: {}",
name, harmonic, percentile, isolation_level, harmonic, caller_count, callee_count, loc
);
let suggested_fix = "\
**Investigate isolated code:**\n\n\
1. **Check if dead code**: Search for usages across the codebase\n\n\
2. **Check if test-only**: May be called only from tests\n\n\
3. **Check if entry point**: CLI commands, API endpoints, etc.\n\n\
4. **Consider removal**: If truly unused, delete it\n\n\
5. **Consider integration**: If needed, integrate properly with the codebase"
.to_string();
let estimated_effort = if loc < 50 {
"Small (15-30 minutes)"
} else {
"Small (30-60 minutes)"
};
Some(Finding {
id: Uuid::new_v4().to_string(),
detector: "CoreUtilityDetector".to_string(),
severity,
title: format!("Isolated code: {} ({})", name, isolation_level),
description,
affected_files: vec![file_path.into()],
line_start: line_number,
line_end: None,
suggested_fix: Some(suggested_fix),
estimated_effort: Some(estimated_effort.to_string()),
category: Some("dead_code".to_string()),
cwe_id: None,
why_it_matters: Some(
"Isolated code increases maintenance burden without providing value. \
It may confuse developers and add cognitive load when navigating the codebase."
.to_string(),
),
..Default::default()
})
}
}
impl Default for CoreUtilityDetector {
fn default() -> Self {
Self::new()
}
}
impl Detector for CoreUtilityDetector {
fn name(&self) -> &'static str {
"CoreUtilityDetector"
}
fn description(&self) -> &'static str {
"Detects central coordinators and isolated code using harmonic centrality"
}
fn category(&self) -> &'static str {
"architecture"
}
fn config(&self) -> Option<&DetectorConfig> {
Some(&self.config)
} fn detect(&self, graph: &GraphStore) -> Result<Vec<Finding>> {
let mut findings = Vec::new();
for func in graph.get_functions() {
let fan_in = graph.call_fan_in(&func.qualified_name);
let fan_out = graph.call_fan_out(&func.qualified_name);
if fan_in >= 10 && fan_out <= 2 {
findings.push(Finding {
id: Uuid::new_v4().to_string(),
detector: "CoreUtilityDetector".to_string(),
severity: Severity::Info,
title: format!("Core Utility: {}", func.name),
description: format!(
"Function '{}' is used by {} callers. This is a core utility - ensure it's well-tested.",
func.name, fan_in
),
affected_files: vec![func.file_path.clone().into()],
line_start: Some(func.line_start),
line_end: Some(func.line_end),
suggested_fix: Some("Ensure comprehensive test coverage for this core function".to_string()),
estimated_effort: Some("Small (1 hour)".to_string()),
category: Some("architecture".to_string()),
cwe_id: None,
why_it_matters: Some("Core utilities need extra attention as bugs affect many callers".to_string()),
..Default::default()
});
}
}
Ok(findings)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_harmonic_centrality_star() {
let detector = CoreUtilityDetector::new();
let adj = vec![
vec![1, 2, 3], vec![0], vec![0], vec![0], ];
let harmonic = detector.calculate_harmonic(&adj, 4, false);
assert!(harmonic[0] > harmonic[1]);
assert!(harmonic[0] > harmonic[2]);
assert!(harmonic[0] > harmonic[3]);
assert!((harmonic[0] - 3.0).abs() < 0.001);
}
#[test]
fn test_harmonic_centrality_chain() {
let detector = CoreUtilityDetector::new();
let adj = vec![
vec![1], vec![0, 2], vec![1, 3], vec![2], ];
let harmonic = detector.calculate_harmonic(&adj, 4, false);
assert!(harmonic[1] > harmonic[0]);
assert!(harmonic[2] > harmonic[3]);
}
}