use crate::detectors::base::{Detector, DetectorConfig};
use crate::detectors::function_context::FunctionRole;
use crate::models::{Finding, LineRange, Severity};
use anyhow::Result;
use tracing::debug;
pub struct InfluentialCodeDetector {
config: DetectorConfig,
high_complexity_threshold: u32,
high_loc_threshold: u32,
pagerank_percentile_threshold: f64,
}
impl InfluentialCodeDetector {
pub fn new() -> Self {
Self {
config: DetectorConfig::new(),
high_complexity_threshold: 15,
high_loc_threshold: 100,
pagerank_percentile_threshold: 0.90,
}
}
pub fn with_config(config: DetectorConfig) -> Self {
let pagerank_percentile_threshold: f64 =
config.get_option_or("pagerank_percentile_threshold", 90) as f64 / 100.0;
Self {
high_complexity_threshold: config.get_option_or("high_complexity_threshold", 15),
high_loc_threshold: config.get_option_or("high_loc_threshold", 100),
pagerank_percentile_threshold,
config,
}
}
fn calculate_severity(
&self,
pagerank_pct: f64,
complexity: usize,
loc: usize,
role: FunctionRole,
) -> Severity {
let base_severity = if pagerank_pct >= 0.99 && complexity >= 20 {
Severity::High
} else if pagerank_pct >= 0.95
&& (complexity >= self.high_complexity_threshold as usize
|| loc >= self.high_loc_threshold as usize)
{
Severity::Medium
} else {
Severity::Low
};
match role {
FunctionRole::Utility => {
if complexity < self.high_complexity_threshold as usize * 2 {
Severity::Low
} else {
base_severity.min(Severity::Medium)
}
}
FunctionRole::Hub => {
base_severity
}
FunctionRole::EntryPoint => {
base_severity.min(Severity::Medium)
}
FunctionRole::Test => Severity::Low,
_ => base_severity,
}
}
fn create_finding(
&self,
name: &str,
file_path: &str,
range: LineRange,
page_rank: f64,
pagerank_pct: f64,
fan_in: usize,
complexity: usize,
loc: usize,
role: FunctionRole,
betweenness: Option<f64>,
) -> Finding {
let severity = self.calculate_severity(pagerank_pct, complexity, loc, role);
let role_note = match role {
FunctionRole::Utility => " (utility - high influence expected)",
FunctionRole::Hub => " (architectural hub)",
FunctionRole::EntryPoint => " (entry point)",
_ => "",
};
let title = format!("Influential Code: {}{}", name, role_note);
let mut description = format!(
"Function '{}' has high transitive influence (PageRank p{:.0}) with \
complexity {} and {} LOC. High-impact code.\n\n\
**Metrics:**\n\
- PageRank: {:.6} (p{:.0})\n\
- Callers (fan-in): {}\n\
- Complexity: {}\n\
- Lines of code: {}",
name,
pagerank_pct * 100.0,
complexity,
loc,
page_rank,
pagerank_pct * 100.0,
fan_in,
complexity,
loc
);
if let Some(b) = betweenness {
description.push_str(&format!("\n- Betweenness centrality: {:.4}", b));
}
let suggested_fix = match role {
FunctionRole::Utility => "This utility is influential but complex. Consider:\n\
- Breaking into smaller, focused helpers\n\
- Adding comprehensive tests"
.to_string(),
FunctionRole::Hub => "This is a critical hub. Consider:\n\
- Ensuring comprehensive test coverage\n\
- Adding monitoring and observability\n\
- Documenting thoroughly"
.to_string(),
_ => {
"Consider refactoring to reduce complexity while maintaining interface".to_string()
}
};
Finding {
id: String::new(),
detector: "InfluentialCodeDetector".to_string(),
severity,
title,
description,
affected_files: vec![file_path.into()],
line_start: Some(range.start),
line_end: Some(range.end),
suggested_fix: Some(suggested_fix),
estimated_effort: Some("Large (4+ hours)".to_string()),
category: Some("architecture".to_string()),
cwe_id: None,
why_it_matters: Some(
"Changes to influential code have wide-reaching effects across the codebase."
.to_string(),
),
..Default::default()
}
}
}
impl Default for InfluentialCodeDetector {
fn default() -> Self {
Self::new()
}
}
impl Detector for InfluentialCodeDetector {
fn name(&self) -> &'static str {
"InfluentialCodeDetector"
}
fn description(&self) -> &'static str {
"Detects influential code using PageRank analysis and function context"
}
fn category(&self) -> &'static str {
"architecture"
}
fn config(&self) -> Option<&DetectorConfig> {
Some(&self.config)
}
fn detect(
&self,
ctx: &crate::detectors::analysis_context::AnalysisContext,
) -> Result<Vec<Finding>> {
let graph = ctx.graph;
let contexts = &ctx.functions;
let i = graph.interner();
let mut findings = Vec::new();
let func_idxs = graph.functions_idx();
debug!(
"InfluentialCodeDetector: analyzing {} functions with PageRank",
func_idxs.len()
);
let mut pagerank_values: Vec<f64> = func_idxs
.iter()
.map(|&idx| {
graph
.primitives()
.page_rank
.get(&idx)
.copied()
.unwrap_or(0.0)
})
.collect();
pagerank_values
.sort_unstable_by(|a, b| a.partial_cmp(b).unwrap_or(std::cmp::Ordering::Equal));
let total = pagerank_values.len();
if total == 0 {
return Ok(findings);
}
let percentile_of = |pr: f64| -> f64 {
if total <= 1 {
return 1.0;
}
let rank = pagerank_values.partition_point(|&v| v < pr);
rank as f64 / total as f64
};
for &func_idx in func_idxs {
let Some(func) = graph.node_idx(func_idx) else {
continue;
};
let fctx = contexts.get(func.qn(i));
if let Some(c) = fctx {
if c.is_test || c.role == FunctionRole::Test {
continue;
}
}
let page_rank = graph
.primitives()
.page_rank
.get(&func_idx)
.copied()
.unwrap_or(0.0);
let pagerank_pct = percentile_of(page_rank);
if pagerank_pct < self.pagerank_percentile_threshold {
continue;
}
let (fan_in, complexity, loc, role, betweenness) = if let Some(c) = fctx {
(
c.in_degree,
c.complexity.unwrap_or(1) as usize,
c.loc as usize,
c.role,
Some(c.betweenness),
)
} else {
let fan_in = graph.call_fan_in_idx(func_idx);
let complexity = func.complexity_opt().unwrap_or(1) as usize;
let loc = func.loc() as usize;
let raw_b = graph
.primitives()
.betweenness
.get(&func_idx)
.copied()
.unwrap_or(0.0);
let betweenness = if raw_b > 0.0 { Some(raw_b) } else { None };
(fan_in, complexity, loc, FunctionRole::Unknown, betweenness)
};
let should_flag = match role {
FunctionRole::Utility => {
complexity >= self.high_complexity_threshold as usize * 2
}
FunctionRole::Hub => {
complexity >= self.high_complexity_threshold as usize
|| loc >= self.high_loc_threshold as usize
}
FunctionRole::EntryPoint => {
complexity >= self.high_complexity_threshold as usize * 2
}
FunctionRole::Test => false,
FunctionRole::Leaf | FunctionRole::Orchestrator | FunctionRole::Unknown => {
complexity >= self.high_complexity_threshold as usize
|| loc >= self.high_loc_threshold as usize
}
};
if should_flag {
findings.push(self.create_finding(
func.node_name(i),
func.path(i),
LineRange::new(func.line_start, func.line_end),
page_rank,
pagerank_pct,
fan_in,
complexity,
loc,
role,
betweenness,
));
}
}
debug!("InfluentialCodeDetector: found {} findings", findings.len());
Ok(findings)
}
}
impl crate::detectors::RegisteredDetector for InfluentialCodeDetector {
fn create(init: &crate::detectors::DetectorInit) -> std::sync::Arc<dyn Detector> {
std::sync::Arc::new(Self::with_config(
init.config_for("InfluentialCodeDetector"),
))
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_severity_with_role() {
let detector = InfluentialCodeDetector::new();
let sev = detector.calculate_severity(0.99, 10, 50, FunctionRole::Utility);
assert_eq!(sev, Severity::Low);
let sev = detector.calculate_severity(0.99, 40, 200, FunctionRole::Utility);
assert_eq!(sev, Severity::Medium);
let sev = detector.calculate_severity(0.99, 25, 100, FunctionRole::Hub);
assert_eq!(sev, Severity::High);
let sev = detector.calculate_severity(0.50, 25, 100, FunctionRole::Hub);
assert_eq!(sev, Severity::Low);
}
#[test]
fn test_severity_thresholds() {
let detector = InfluentialCodeDetector::new();
assert_eq!(
detector.calculate_severity(0.99, 20, 50, FunctionRole::Unknown),
Severity::High
);
assert_eq!(
detector.calculate_severity(0.96, 15, 50, FunctionRole::Unknown),
Severity::Medium
);
assert_eq!(
detector.calculate_severity(0.96, 5, 50, FunctionRole::Unknown),
Severity::Low
);
}
#[test]
fn test_default_percentile_threshold() {
let detector = InfluentialCodeDetector::new();
assert!((detector.pagerank_percentile_threshold - 0.90).abs() < f64::EPSILON);
}
}