mod ast_analysis;
mod classifiers;
mod pattern_matchers;
use crate::analyzers::rust_data_flow_analyzer::analyze_data_flow;
use crate::core::FunctionMetrics;
use crate::priority::call_graph::{CallGraph, FunctionId};
use ast_analysis::is_simple_accessor_body;
use classifiers::{
is_constructor_enhanced, is_debug_function, is_enum_converter_enhanced, is_io_wrapper,
is_orchestrator, is_pattern_matching_function,
};
use pattern_matchers::{is_entry_point_by_name, matches_accessor_name};
use serde::{Deserialize, Serialize};
#[cfg(test)]
use classifiers::{
calculate_delegation_ratio, delegates_to_tested_functions, has_diagnostic_characteristics,
is_simple_constructor,
};
#[cfg(test)]
use pattern_matchers::matches_debug_pattern;
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
pub enum FunctionRole {
PureLogic, Orchestrator, IOWrapper, EntryPoint, PatternMatch, Debug, Unknown, }
pub fn classify_function_role(
func: &FunctionMetrics,
func_id: &FunctionId,
call_graph: &CallGraph,
) -> FunctionRole {
classify_by_rules(func, func_id, call_graph, None).unwrap_or_else(|| {
if is_impure_function(func) {
FunctionRole::Unknown
} else {
FunctionRole::PureLogic
}
})
}
fn classify_by_rules(
func: &FunctionMetrics,
func_id: &FunctionId,
call_graph: &CallGraph,
syn_func: Option<&syn::ItemFn>,
) -> Option<FunctionRole> {
if is_entry_point(func_id, call_graph) {
return Some(FunctionRole::EntryPoint);
}
if is_debug_function(func) {
return Some(FunctionRole::Debug);
}
if is_constructor_enhanced(func, syn_func) {
return Some(FunctionRole::IOWrapper);
}
if let Some(syn_func) = syn_func {
if is_enum_converter_enhanced(func, syn_func) {
return Some(FunctionRole::IOWrapper);
}
}
if is_accessor_method(func, syn_func) {
return Some(FunctionRole::IOWrapper);
}
if let Some(syn_func) = syn_func {
let config = crate::config::get_data_flow_classification_config();
if config.enabled {
let profile = analyze_data_flow(syn_func);
if profile.confidence >= config.min_confidence
&& profile.transformation_ratio >= config.min_transformation_ratio
&& profile.business_logic_ratio < config.max_business_logic_ratio
{
return Some(FunctionRole::Orchestrator);
}
}
}
if is_pattern_matching_function(func, func_id) {
return Some(FunctionRole::PatternMatch);
}
if is_io_wrapper(func) {
return Some(FunctionRole::IOWrapper);
}
if is_orchestrator(func, func_id, call_graph) {
return Some(FunctionRole::Orchestrator);
}
None }
fn is_entry_point(func_id: &FunctionId, call_graph: &CallGraph) -> bool {
call_graph.is_entry_point(func_id) || is_entry_point_by_name(&func_id.name)
}
fn is_impure_function(func: &FunctionMetrics) -> bool {
use crate::core::PurityLevel;
if let Some(level) = func.purity_level {
return level == PurityLevel::Impure;
}
func.is_pure == Some(false)
}
fn is_accessor_method(func: &FunctionMetrics, syn_func: Option<&syn::ItemFn>) -> bool {
let config = crate::config::get_accessor_detection_config();
if !config.enabled {
return false;
}
if !matches_accessor_name(&func.name, &config) {
return false;
}
if func.cyclomatic > config.max_cyclomatic
|| func.cognitive > config.max_cognitive
|| func.length >= config.max_length
|| func.nesting > config.max_nesting
{
return false;
}
if let Some(syn_func) = syn_func {
if !is_simple_accessor_body(syn_func) {
return false;
}
}
true
}
pub fn get_role_multiplier(role: FunctionRole) -> f64 {
let config = crate::config::get_role_multipliers();
match role {
FunctionRole::PureLogic => config.pure_logic,
FunctionRole::Orchestrator => config.orchestrator,
FunctionRole::IOWrapper => config.io_wrapper,
FunctionRole::EntryPoint => config.entry_point,
FunctionRole::PatternMatch => config.pattern_match,
FunctionRole::Debug => config.debug,
FunctionRole::Unknown => config.unknown,
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::core::FunctionMetrics;
use crate::priority::call_graph::{CallGraph, CallType, FunctionCall, FunctionId};
use std::path::PathBuf;
fn create_test_metrics(
name: &str,
cyclomatic: u32,
cognitive: u32,
lines: usize,
) -> FunctionMetrics {
FunctionMetrics {
file: PathBuf::from("test.rs"),
name: name.to_string(),
line: 1,
length: lines,
cyclomatic,
cognitive,
nesting: 0,
is_test: false,
visibility: None,
is_trait_method: false,
in_test_module: false,
entropy_score: None,
is_pure: None,
purity_confidence: None,
purity_reason: None,
call_dependencies: None,
detected_patterns: None,
upstream_callers: None,
downstream_callees: None,
mapping_pattern_result: None,
adjusted_complexity: None,
composition_metrics: None,
language_specific: None,
purity_level: None,
error_swallowing_count: None,
error_swallowing_patterns: None,
entropy_analysis: None,
}
}
#[test]
fn test_entry_point_classification() {
let graph = CallGraph::new();
let func = create_test_metrics("main", 5, 8, 50);
let func_id = FunctionId::new(PathBuf::from("main.rs"), "main".to_string(), 1);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::EntryPoint);
}
#[test]
fn test_orchestrator_classification() {
let mut graph = CallGraph::new();
let func = create_test_metrics("coordinate_tasks", 2, 3, 15);
let func_id = FunctionId::new(
PathBuf::from("coord.rs"),
"coordinate_tasks".to_string(),
10,
);
graph.add_function(func_id.clone(), false, false, 2, 15);
for i in 0..3 {
let worker_id =
FunctionId::new(PathBuf::from("worker.rs"), format!("worker_{i}"), i * 10);
graph.add_function(worker_id.clone(), false, false, 8, 40);
graph.add_call(crate::priority::call_graph::FunctionCall {
caller: func_id.clone(),
callee: worker_id,
call_type: crate::priority::call_graph::CallType::Delegate,
});
}
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::Orchestrator);
}
#[test]
fn test_io_wrapper_classification() {
let graph = CallGraph::new();
let func = create_test_metrics("read_file", 1, 2, 10);
let func_id = FunctionId::new(PathBuf::from("io.rs"), "read_file".to_string(), 5);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::IOWrapper);
}
#[test]
fn test_io_orchestration_classification() {
let graph = CallGraph::new();
let mut func = create_test_metrics("output_unified_priorities", 12, 19, 38);
func.nesting = 3;
let func_id = FunctionId::new(
PathBuf::from("main.rs"),
"output_unified_priorities".to_string(),
861,
);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::IOWrapper);
func.nesting = 4;
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::PureLogic);
}
#[test]
fn test_pure_logic_classification() {
let graph = CallGraph::new();
let func = create_test_metrics("calculate_risk", 8, 12, 60);
let func_id = FunctionId::new(PathBuf::from("calc.rs"), "calculate_risk".to_string(), 20);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(role, FunctionRole::PureLogic);
}
#[test]
fn test_role_multipliers() {
assert_eq!(get_role_multiplier(FunctionRole::PureLogic), 0.7);
assert_eq!(get_role_multiplier(FunctionRole::Orchestrator), 0.8);
assert_eq!(get_role_multiplier(FunctionRole::IOWrapper), 0.7);
assert_eq!(get_role_multiplier(FunctionRole::EntryPoint), 0.9);
assert_eq!(get_role_multiplier(FunctionRole::PatternMatch), 0.6);
assert_eq!(get_role_multiplier(FunctionRole::Unknown), 1.0);
}
#[test]
fn test_formatting_function_not_orchestrator() {
let mut graph = CallGraph::new();
let func = create_test_metrics("format_recommendation_box_header", 1, 0, 9);
let func_id = FunctionId::new(
PathBuf::from("insights.rs"),
"format_recommendation_box_header".to_string(),
142,
);
graph.add_function(func_id.clone(), false, false, 1, 9);
let callee1 = FunctionId::new(
PathBuf::from("insights.rs"),
"calculate_dash_count".to_string(),
138,
);
let callee2 = FunctionId::new(PathBuf::from("std"), "format".to_string(), 1);
graph.add_function(callee1.clone(), false, false, 1, 3);
graph.add_function(callee2.clone(), false, false, 1, 1);
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee1,
call_type: CallType::Direct,
});
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee2,
call_type: CallType::Direct,
});
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::PureLogic,
"Formatting function should be PureLogic, not Orchestrator"
);
assert!(
!delegates_to_tested_functions(&func_id, &graph, 0.8),
"Should not be considered delegation when calling std functions"
);
}
#[test]
fn test_actual_orchestrator_with_meaningful_callees() {
let mut graph = CallGraph::new();
let func = create_test_metrics("coordinate_workflow", 2, 3, 15);
let func_id = FunctionId::new(
PathBuf::from("workflow.rs"),
"coordinate_workflow".to_string(),
10,
);
graph.add_function(func_id.clone(), false, false, 2, 15);
let callee1 = FunctionId::new(
PathBuf::from("workflow.rs"),
"process_step_one".to_string(),
50,
);
let callee2 = FunctionId::new(
PathBuf::from("workflow.rs"),
"process_step_two".to_string(),
100,
);
let callee3 = FunctionId::new(
PathBuf::from("workflow.rs"),
"process_step_three".to_string(),
150,
);
graph.add_function(callee1.clone(), false, false, 5, 30);
graph.add_function(callee2.clone(), false, false, 5, 30);
graph.add_function(callee3.clone(), false, false, 5, 30);
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee1,
call_type: CallType::Direct,
});
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee2,
call_type: CallType::Direct,
});
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee3,
call_type: CallType::Direct,
});
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::Orchestrator,
"Function coordinating multiple steps should be Orchestrator"
);
}
#[test]
fn test_orchestrator_with_error_handling_complexity_5() {
let mut graph = CallGraph::new();
let func = create_test_metrics("coordinate_tasks", 5, 3, 20);
let func_id = FunctionId::new(
PathBuf::from("tasks.rs"),
"coordinate_tasks".to_string(),
10,
);
graph.add_function(func_id.clone(), false, false, 5, 20);
for i in 0..4 {
let callee_id = FunctionId::new(
PathBuf::from("worker.rs"),
format!("worker_task_{}", i),
i * 20,
);
graph.add_function(callee_id.clone(), false, false, 8, 40);
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee_id,
call_type: CallType::Direct,
});
}
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::Orchestrator,
"Function with complexity 5 and good delegation ratio should be Orchestrator"
);
}
#[test]
fn test_delegation_ratio_calculation() {
let func = create_test_metrics("orchestrator", 4, 2, 20);
let callees_vec: Vec<FunctionId> = (0..4)
.map(|i| FunctionId::new(PathBuf::from("test.rs"), format!("callee_{}", i), i * 10))
.collect();
let callees: Vec<&FunctionId> = callees_vec.iter().collect();
let ratio = calculate_delegation_ratio(&func, &callees);
assert!(
(ratio - 0.2).abs() < 0.01,
"Expected delegation ratio of 0.2, got {}",
ratio
);
}
#[test]
fn test_high_complexity_not_orchestrator() {
let mut graph = CallGraph::new();
let func = create_test_metrics("complex_logic", 8, 10, 30);
let func_id = FunctionId::new(PathBuf::from("logic.rs"), "complex_logic".to_string(), 10);
graph.add_function(func_id.clone(), false, false, 8, 30);
for i in 0..3 {
let callee_id =
FunctionId::new(PathBuf::from("worker.rs"), format!("worker_{}", i), i * 20);
graph.add_function(callee_id.clone(), false, false, 5, 20);
graph.add_call(FunctionCall {
caller: func_id.clone(),
callee: callee_id,
call_type: CallType::Direct,
});
}
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::PureLogic,
"Function with complexity > 5 should be PureLogic, not Orchestrator"
);
}
#[test]
fn test_simple_constructor_detection() {
let func = create_test_metrics("any", 1, 0, 9);
assert!(
is_simple_constructor(&func),
"any() should be detected as constructor"
);
let func = create_test_metrics("new", 1, 0, 5);
assert!(
is_simple_constructor(&func),
"new() should be detected as constructor"
);
let func = create_test_metrics("from_config", 1, 0, 8);
assert!(
is_simple_constructor(&func),
"from_config() should be detected as constructor"
);
let func = create_test_metrics("with_defaults", 2, 1, 12);
assert!(
is_simple_constructor(&func),
"with_defaults() should be detected as constructor"
);
let func = create_test_metrics("create_complex", 8, 12, 50);
assert!(
!is_simple_constructor(&func),
"Complex function should NOT be detected as constructor"
);
let func = create_test_metrics("new_complex", 2, 2, 25);
assert!(
!is_simple_constructor(&func),
"Long function should NOT be detected as constructor"
);
let func = create_test_metrics("new_with_logic", 2, 8, 10);
assert!(
!is_simple_constructor(&func),
"High cognitive complexity should NOT be detected as constructor"
);
}
#[test]
fn test_constructor_classification_precedence() {
let graph = CallGraph::new();
let func = create_test_metrics("any", 1, 0, 9);
let func_id = FunctionId::new(PathBuf::from("context/rules.rs"), "any".to_string(), 52);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::IOWrapper,
"Simple constructor should be classified as IOWrapper, not PureLogic"
);
}
#[test]
fn test_constructor_name_patterns() {
assert!(is_simple_constructor(&create_test_metrics("new", 1, 0, 5)));
assert!(is_simple_constructor(&create_test_metrics(
"default", 1, 0, 5
)));
assert!(is_simple_constructor(&create_test_metrics(
"empty", 1, 0, 5
)));
assert!(is_simple_constructor(&create_test_metrics("zero", 1, 0, 5)));
assert!(is_simple_constructor(&create_test_metrics(
"from_str", 1, 0, 8
)));
assert!(is_simple_constructor(&create_test_metrics(
"with_capacity",
2,
1,
10
)));
assert!(is_simple_constructor(&create_test_metrics(
"create_default",
1,
0,
7
)));
assert!(is_simple_constructor(&create_test_metrics(
"make_instance",
1,
0,
6
)));
assert!(is_simple_constructor(&create_test_metrics(
"build_config",
2,
2,
12
)));
let func = create_test_metrics("calculate_score", 1, 0, 5);
assert!(
!is_simple_constructor(&func),
"Non-constructor name should not match"
);
}
#[test]
fn test_constructor_complexity_thresholds() {
let func = create_test_metrics("new", 2, 3, 14);
assert!(
is_simple_constructor(&func),
"At threshold limits should match"
);
let func = create_test_metrics("new", 3, 2, 10);
assert!(
!is_simple_constructor(&func),
"Over cyclomatic threshold should not match"
);
let func = create_test_metrics("new", 1, 4, 10);
assert!(
!is_simple_constructor(&func),
"Over cognitive threshold should not match"
);
let func = create_test_metrics("new", 1, 2, 15);
assert!(
!is_simple_constructor(&func),
"Over length threshold should not match"
);
let mut func = create_test_metrics("new", 1, 2, 10);
func.nesting = 2;
assert!(
!is_simple_constructor(&func),
"Over nesting threshold should not match"
);
}
#[test]
fn test_ast_detects_non_standard_constructor() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn create_default_client() -> Self {
Self {
timeout: Duration::from_secs(30),
retries: 3,
}
}
};
let func = create_test_metrics("create_default_client", 1, 0, 5);
assert!(
is_constructor_enhanced(&func, Some(&source)),
"AST should detect non-standard constructor name"
);
assert!(
is_constructor_enhanced(&func, None),
"Should fallback to name-based detection"
);
}
#[test]
fn test_ast_detects_result_self_constructor() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn try_new(value: i32) -> Result<Self, Error> {
if value > 0 {
Ok(Self { value })
} else {
Err(Error::InvalidValue)
}
}
};
let func = create_test_metrics("try_new", 2, 1, 8);
assert!(
is_constructor_enhanced(&func, Some(&source)),
"AST should detect Result<Self> constructor"
);
}
#[test]
fn test_ast_rejects_loop_in_constructor() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn process_items() -> Self {
let mut result = Self::new();
for item in items {
result.add(item);
}
result
}
};
let func = create_test_metrics("process_items", 2, 3, 8);
assert!(
!is_constructor_enhanced(&func, Some(&source)),
"AST should reject constructors with loops"
);
}
#[test]
fn test_ast_fallback_when_not_returning_self() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn calculate_value() -> i32 {
42
}
};
let func = create_test_metrics("calculate_value", 1, 0, 3);
assert!(
!is_constructor_enhanced(&func, Some(&source)),
"AST should fallback when not returning Self"
);
}
#[test]
fn test_enum_converter_framework_type_name() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn name(&self) -> &'static str {
match self {
FrameworkType::Django => "Django",
FrameworkType::Flask => "Flask",
FrameworkType::PyQt => "PyQt",
}
}
};
let func = create_test_metrics("name", 3, 0, 7);
assert!(
is_enum_converter_enhanced(&func, &source),
"FrameworkType::name should be detected as enum converter"
);
}
#[test]
fn test_enum_converter_builtin_exception_as_str() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
fn as_str(&self) -> &str {
match self {
Self::BaseException => "BaseException",
Self::ValueError => "ValueError",
Self::TypeError => "TypeError",
}
}
};
let func = create_test_metrics("as_str", 3, 0, 7);
assert!(
is_enum_converter_enhanced(&func, &source),
"BuiltinException::as_str should be detected as enum converter"
);
}
#[test]
fn test_enum_converter_with_function_calls_not_detected() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn process(&self) -> String {
match self {
Variant::A => format!("A"),
Variant::B => format!("B"),
}
}
};
let func = create_test_metrics("process", 2, 1, 7);
assert!(
!is_enum_converter_enhanced(&func, &source),
"Converter with function calls should NOT be detected"
);
}
#[test]
fn test_enum_converter_high_cognitive_complexity_rejected() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn name(&self) -> &'static str {
match self {
Type::A => "A",
Type::B => "B",
}
}
};
let func = create_test_metrics("name", 2, 5, 7);
assert!(
!is_enum_converter_enhanced(&func, &source),
"High cognitive complexity should be rejected"
);
}
#[test]
fn test_enum_converter_classification_precedence() {
use syn::parse_quote;
let graph = CallGraph::new();
let source: syn::ItemFn = parse_quote! {
pub fn name(&self) -> &'static str {
match self {
FrameworkType::Django => "Django",
FrameworkType::Flask => "Flask",
}
}
};
let func = create_test_metrics("name", 2, 0, 7);
let func_id = FunctionId::new(PathBuf::from("framework.rs"), "name".to_string(), 10);
let role = classify_by_rules(&func, &func_id, &graph, Some(&source));
assert_eq!(
role,
Some(FunctionRole::IOWrapper),
"Enum converter should be classified as IOWrapper"
);
}
#[test]
fn test_ast_detection_can_be_disabled() {
use syn::parse_quote;
let source: syn::ItemFn = parse_quote! {
pub fn create_default_client() -> Self {
Self { field: 0 }
}
};
let func = create_test_metrics("create_default_client", 1, 0, 5);
assert!(
is_constructor_enhanced(&func, Some(&source)),
"Should detect with AST when enabled"
);
assert!(
is_constructor_enhanced(&func, None),
"Should fallback to name-based when AST unavailable"
);
}
#[test]
fn test_simple_field_accessor_detected() {
let metrics = create_test_metrics("id", 1, 0, 3);
assert!(
is_accessor_method(&metrics, None),
"id() should be detected as accessor"
);
let metrics = create_test_metrics("get_name", 1, 0, 5);
assert!(
is_accessor_method(&metrics, None),
"get_name() should be detected as accessor"
);
}
#[test]
fn test_bool_accessor_detected() {
let metrics = create_test_metrics("is_active", 2, 1, 8);
assert!(
is_accessor_method(&metrics, None),
"is_active() should be detected as accessor"
);
let metrics = create_test_metrics("has_permission", 2, 0, 5);
assert!(
is_accessor_method(&metrics, None),
"has_permission() should be detected as accessor"
);
}
#[test]
fn test_converter_method_detected() {
let metrics = create_test_metrics("as_str", 1, 0, 3);
assert!(
is_accessor_method(&metrics, None),
"as_str() should be detected as accessor"
);
let metrics = create_test_metrics("to_string", 1, 0, 4);
assert!(
is_accessor_method(&metrics, None),
"to_string() should be detected as accessor"
);
}
#[test]
fn test_complex_method_not_detected_as_accessor() {
let metrics = create_test_metrics("get_value", 5, 3, 20);
assert!(
!is_accessor_method(&metrics, None),
"Complex method should not be detected as accessor"
);
}
#[test]
fn test_business_logic_not_misclassified_as_accessor() {
let metrics = create_test_metrics("calculate_total", 4, 2, 15);
assert!(
!is_accessor_method(&metrics, None),
"Business logic should not be detected as accessor"
);
}
#[test]
fn test_ast_body_validation_for_accessor() {
use syn::parse_quote;
let code: syn::ItemFn = parse_quote! {
pub fn id(&self) -> u32 {
self.id
}
};
assert!(
is_simple_accessor_body(&code),
"Simple field access should be valid accessor body"
);
}
#[test]
fn test_accessor_side_effect_rejected() {
use syn::parse_quote;
let code: syn::ItemFn = parse_quote! {
pub fn get_value(&self) -> i32 {
self.counter.fetch_add(1, Ordering::SeqCst);
self.value
}
};
assert!(
!is_simple_accessor_body(&code),
"Accessor with side effects should be rejected"
);
}
#[test]
fn test_accessor_classification_integration() {
let graph = CallGraph::new();
let func = create_test_metrics("id", 1, 0, 3);
let func_id = FunctionId::new(PathBuf::from("user.rs"), "id".to_string(), 10);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::IOWrapper,
"Simple accessor should be classified as IOWrapper"
);
}
#[test]
fn test_accessor_with_reference_return() {
use syn::parse_quote;
let code: syn::ItemFn = parse_quote! {
pub fn name(&self) -> &str {
&self.name
}
};
assert!(
is_simple_accessor_body(&code),
"Reference to field should be valid accessor body"
);
}
#[test]
fn test_accessor_with_method_call() {
use syn::parse_quote;
let code: syn::ItemFn = parse_quote! {
pub fn name(&self) -> String {
self.name.clone()
}
};
assert!(
is_simple_accessor_body(&code),
"Field with .clone() should be valid accessor body"
);
}
#[test]
fn test_accessor_single_word_patterns() {
for name in &[
"id", "name", "value", "kind", "type", "status", "code", "key", "index",
] {
let metrics = create_test_metrics(name, 1, 0, 3);
assert!(
is_accessor_method(&metrics, None),
"{} should be detected as accessor",
name
);
}
}
#[test]
fn test_accessor_prefix_patterns() {
for (name, expected) in &[
("get_value", true),
("is_active", true),
("has_data", true),
("can_execute", true),
("should_run", true),
("as_str", true),
("to_string", true),
("into_iter", true),
] {
let metrics = create_test_metrics(name, 1, 0, 5);
assert_eq!(
is_accessor_method(&metrics, None),
*expected,
"{} accessor detection mismatch",
name
);
}
}
#[test]
fn test_accessor_complexity_thresholds() {
let metrics = create_test_metrics("get_value", 2, 1, 9);
assert!(
is_accessor_method(&metrics, None),
"At threshold should be detected"
);
let metrics = create_test_metrics("get_value", 3, 1, 9);
assert!(
!is_accessor_method(&metrics, None),
"Over cyclomatic threshold should not be detected"
);
let metrics = create_test_metrics("get_value", 2, 2, 9);
assert!(
!is_accessor_method(&metrics, None),
"Over cognitive threshold should not be detected"
);
let metrics = create_test_metrics("get_value", 2, 1, 10);
assert!(
!is_accessor_method(&metrics, None),
"At or over length threshold should not be detected"
);
}
#[test]
fn test_accessor_requires_immutable_self() {
use syn::parse_quote;
let code: syn::ItemFn = parse_quote! {
pub fn value(&self) -> i32 {
self.value
}
};
assert!(
is_simple_accessor_body(&code),
"Immutable self should be valid"
);
let code: syn::ItemFn = parse_quote! {
pub fn value(&mut self) -> i32 {
self.value
}
};
assert!(
!is_simple_accessor_body(&code),
"Mutable self should be rejected"
);
}
#[test]
fn test_accessor_precedence_in_classification() {
use syn::parse_quote;
let graph = CallGraph::new();
let code: syn::ItemFn = parse_quote! {
pub fn id(&self) -> u32 {
self.id
}
};
let func = create_test_metrics("id", 1, 0, 3);
let func_id = FunctionId::new(PathBuf::from("test.rs"), "id".to_string(), 10);
let role = classify_by_rules(&func, &func_id, &graph, Some(&code));
assert_eq!(
role,
Some(FunctionRole::IOWrapper),
"Accessor should be classified as IOWrapper"
);
}
#[test]
fn test_debug_function_with_diagnostics_suffix() {
let func = create_test_metrics("handle_call_graph_diagnostics", 5, 3, 20);
assert!(
is_debug_function(&func),
"Function with _diagnostics suffix should be detected as debug"
);
}
#[test]
fn test_debug_function_with_debug_prefix() {
let func = create_test_metrics("debug_print_info", 3, 2, 15);
assert!(
is_debug_function(&func),
"Function with debug_ prefix should be detected as debug"
);
}
#[test]
fn test_print_function_detected_as_debug() {
let func = create_test_metrics("print_statistics", 4, 3, 18);
assert!(
is_debug_function(&func),
"Function with print_ prefix should be detected as debug"
);
}
#[test]
fn test_dump_function_detected_as_debug() {
let func = create_test_metrics("dump_state", 2, 1, 10);
assert!(
is_debug_function(&func),
"Function with dump_ prefix should be detected as debug"
);
}
#[test]
fn test_trace_function_detected_as_debug() {
let func = create_test_metrics("trace_execution", 3, 2, 12);
assert!(
is_debug_function(&func),
"Function with trace_ prefix should be detected as debug"
);
}
#[test]
fn test_high_complexity_debug_name_not_debug() {
let func = create_test_metrics("debug_complex_algorithm", 15, 12, 50);
assert!(
!is_debug_function(&func),
"High complexity function should not be classified as debug even with debug name"
);
}
#[test]
fn test_debug_stats_function() {
let func = create_test_metrics("calculate_stats", 4, 3, 20);
assert!(
is_debug_function(&func),
"Function ending with _stats should be detected as debug"
);
}
#[test]
fn test_debug_classification_integration() {
let graph = CallGraph::new();
let func = create_test_metrics("print_call_graph_diagnostics", 5, 3, 20);
let func_id = FunctionId::new(
PathBuf::from("commands/analyze.rs"),
"print_call_graph_diagnostics".to_string(),
396,
);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::Debug,
"Diagnostic function should be classified as Debug role"
);
}
#[test]
fn test_entry_point_takes_precedence_over_debug() {
let graph = CallGraph::new();
let func = create_test_metrics("handle_diagnostics", 5, 3, 20);
let func_id = FunctionId::new(
PathBuf::from("commands/analyze.rs"),
"handle_diagnostics".to_string(),
396,
);
let role = classify_function_role(&func, &func_id, &graph);
assert_eq!(
role,
FunctionRole::EntryPoint,
"Entry point pattern should take precedence over debug pattern"
);
}
#[test]
fn test_business_logic_not_misclassified_as_debug() {
let func = create_test_metrics("calculate_complexity", 8, 5, 30);
assert!(
!is_debug_function(&func),
"Business logic function should not be detected as debug"
);
}
#[test]
fn test_debug_role_multiplier() {
let multiplier = get_role_multiplier(FunctionRole::Debug);
assert_eq!(
multiplier, 0.3,
"Debug role should have lowest multiplier (0.3)"
);
}
#[test]
fn test_matches_debug_pattern() {
assert!(matches_debug_pattern("debug_foo"));
assert!(matches_debug_pattern("print_bar"));
assert!(matches_debug_pattern("dump_state"));
assert!(matches_debug_pattern("trace_execution"));
assert!(matches_debug_pattern("foo_diagnostics"));
assert!(matches_debug_pattern("bar_debug"));
assert!(matches_debug_pattern("baz_stats"));
assert!(matches_debug_pattern("test_diagnostics_helper"));
assert!(!matches_debug_pattern("calculate"));
assert!(!matches_debug_pattern("process"));
assert!(!matches_debug_pattern("validate"));
}
#[test]
fn test_diagnostic_characteristics() {
let func = create_test_metrics("print_output", 3, 2, 15);
assert!(
has_diagnostic_characteristics(&func),
"Simple print function should have diagnostic characteristics"
);
let func = create_test_metrics("print_complex", 10, 8, 40);
assert!(
!has_diagnostic_characteristics(&func),
"Complex function should not have diagnostic characteristics"
);
let func = create_test_metrics("calculate_total", 3, 2, 15);
assert!(
!has_diagnostic_characteristics(&func),
"Non-output function should not have diagnostic characteristics"
);
let func = create_test_metrics("read_file", 3, 2, 15);
assert!(
!has_diagnostic_characteristics(&func),
"Read/write operations should not have diagnostic characteristics"
);
}
}