1mod ast_analysis;
2mod classifiers;
3mod pattern_matchers;
4
5use crate::analyzers::rust_data_flow_analyzer::analyze_data_flow;
6use crate::core::FunctionMetrics;
7use crate::priority::call_graph::{CallGraph, FunctionId};
8use ast_analysis::is_simple_accessor_body;
9use classifiers::{
10 is_constructor_enhanced, is_debug_function, is_enum_converter_enhanced, is_io_wrapper,
11 is_orchestrator, is_pattern_matching_function,
12};
13use pattern_matchers::{is_entry_point_by_name, matches_accessor_name};
14use serde::{Deserialize, Serialize};
15
16#[cfg(test)]
17use classifiers::{
18 calculate_delegation_ratio, delegates_to_tested_functions, has_diagnostic_characteristics,
19 is_simple_constructor,
20};
21#[cfg(test)]
22use pattern_matchers::matches_debug_pattern;
23
24#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
25pub enum FunctionRole {
26 PureLogic, Orchestrator, IOWrapper, EntryPoint, PatternMatch, Debug, Unknown, }
34
35pub fn classify_function_role(
36 func: &FunctionMetrics,
37 func_id: &FunctionId,
38 call_graph: &CallGraph,
39) -> FunctionRole {
40 classify_by_rules(func, func_id, call_graph, None).unwrap_or_else(|| {
44 if is_impure_function(func) {
47 FunctionRole::Unknown
48 } else {
49 FunctionRole::PureLogic
50 }
51 })
52}
53
54fn classify_by_rules(
56 func: &FunctionMetrics,
57 func_id: &FunctionId,
58 call_graph: &CallGraph,
59 syn_func: Option<&syn::ItemFn>,
60) -> Option<FunctionRole> {
61 if is_entry_point(func_id, call_graph) {
63 return Some(FunctionRole::EntryPoint);
64 }
65
66 if is_debug_function(func) {
68 return Some(FunctionRole::Debug);
69 }
70
71 if is_constructor_enhanced(func, syn_func) {
73 return Some(FunctionRole::IOWrapper);
74 }
75
76 if let Some(syn_func) = syn_func
78 && is_enum_converter_enhanced(func, syn_func)
79 {
80 return Some(FunctionRole::IOWrapper);
81 }
82
83 if is_accessor_method(func, syn_func) {
86 return Some(FunctionRole::IOWrapper);
87 }
88
89 if let Some(syn_func) = syn_func {
91 let config = crate::config::get_data_flow_classification_config();
92
93 if config.enabled {
94 let profile = analyze_data_flow(syn_func);
95
96 if profile.confidence >= config.min_confidence
98 && profile.transformation_ratio >= config.min_transformation_ratio
99 && profile.business_logic_ratio < config.max_business_logic_ratio
100 {
101 return Some(FunctionRole::Orchestrator);
102 }
103 }
104 }
105
106 if is_pattern_matching_function(func, func_id) {
108 return Some(FunctionRole::PatternMatch);
109 }
110
111 if is_io_wrapper(func) {
113 return Some(FunctionRole::IOWrapper);
114 }
115
116 if is_orchestrator(func, func_id, call_graph) {
118 return Some(FunctionRole::Orchestrator);
119 }
120
121 None }
123
124fn is_entry_point(func_id: &FunctionId, call_graph: &CallGraph) -> bool {
126 call_graph.is_entry_point(func_id) || is_entry_point_by_name(&func_id.name)
127}
128
129fn is_impure_function(func: &FunctionMetrics) -> bool {
136 use crate::core::PurityLevel;
137
138 if let Some(level) = func.purity_level {
140 return level == PurityLevel::Impure;
141 }
142
143 func.is_pure == Some(false)
145}
146
147fn is_accessor_method(func: &FunctionMetrics, syn_func: Option<&syn::ItemFn>) -> bool {
174 let config = crate::config::get_accessor_detection_config();
175
176 if !config.enabled {
178 return false;
179 }
180
181 if !matches_accessor_name(&func.name, &config) {
183 return false;
184 }
185
186 if func.cyclomatic > config.max_cyclomatic
188 || func.cognitive > config.max_cognitive
189 || func.length >= config.max_length
190 || func.nesting > config.max_nesting
191 {
192 return false;
193 }
194
195 if let Some(syn_func) = syn_func
197 && !is_simple_accessor_body(syn_func)
198 {
199 return false;
200 }
201
202 true
203}
204
205pub fn get_role_multiplier(role: FunctionRole) -> f64 {
206 let config = crate::config::get_role_multipliers();
208
209 match role {
210 FunctionRole::PureLogic => config.pure_logic,
211 FunctionRole::Orchestrator => config.orchestrator,
212 FunctionRole::IOWrapper => config.io_wrapper,
213 FunctionRole::EntryPoint => config.entry_point,
214 FunctionRole::PatternMatch => config.pattern_match,
215 FunctionRole::Debug => config.debug,
216 FunctionRole::Unknown => config.unknown,
217 }
218}
219
220#[cfg(test)]
224mod tests {
225 use super::*;
226 use crate::core::FunctionMetrics;
227 use crate::priority::call_graph::{CallGraph, CallType, FunctionCall, FunctionId};
228 use std::path::PathBuf;
229
230 fn create_test_metrics(
231 name: &str,
232 cyclomatic: u32,
233 cognitive: u32,
234 lines: usize,
235 ) -> FunctionMetrics {
236 FunctionMetrics {
237 file: PathBuf::from("test.rs"),
238 name: name.to_string(),
239 line: 1,
240 length: lines,
241 cyclomatic,
242 cognitive,
243 nesting: 0,
244 is_test: false,
245 visibility: None,
246 is_trait_method: false,
247 in_test_module: false,
248 entropy_score: None,
249 is_pure: None,
250 purity_confidence: None,
251 purity_reason: None,
252 call_dependencies: None,
253 detected_patterns: None,
254 upstream_callers: None,
255 downstream_callees: None,
256 mapping_pattern_result: None,
257 adjusted_complexity: None,
258 composition_metrics: None,
259 language_specific: None,
260 purity_level: None,
261 error_swallowing_count: None,
262 error_swallowing_patterns: None,
263 entropy_analysis: None,
264 }
265 }
266
267 #[test]
268 fn test_entry_point_classification() {
269 let graph = CallGraph::new();
270 let func = create_test_metrics("main", 5, 8, 50);
271 let func_id = FunctionId::new(PathBuf::from("main.rs"), "main".to_string(), 1);
272
273 let role = classify_function_role(&func, &func_id, &graph);
274 assert_eq!(role, FunctionRole::EntryPoint);
275 }
276
277 #[test]
278 fn test_orchestrator_classification() {
279 let mut graph = CallGraph::new();
280 let func = create_test_metrics("coordinate_tasks", 2, 3, 15);
281 let func_id = FunctionId::new(
282 PathBuf::from("coord.rs"),
283 "coordinate_tasks".to_string(),
284 10,
285 );
286
287 graph.add_function(func_id.clone(), false, false, 2, 15);
289
290 for i in 0..3 {
292 let worker_id =
293 FunctionId::new(PathBuf::from("worker.rs"), format!("worker_{i}"), i * 10);
294 graph.add_function(worker_id.clone(), false, false, 8, 40);
295 graph.add_call(crate::priority::call_graph::FunctionCall {
296 caller: func_id.clone(),
297 callee: worker_id,
298 call_type: crate::priority::call_graph::CallType::Delegate,
299 });
300 }
301
302 let role = classify_function_role(&func, &func_id, &graph);
303 assert_eq!(role, FunctionRole::Orchestrator);
304 }
305
306 #[test]
307 fn test_io_wrapper_classification() {
308 let graph = CallGraph::new();
309 let func = create_test_metrics("read_file", 1, 2, 10);
310 let func_id = FunctionId::new(PathBuf::from("io.rs"), "read_file".to_string(), 5);
311
312 let role = classify_function_role(&func, &func_id, &graph);
313 assert_eq!(role, FunctionRole::IOWrapper);
314 }
315
316 #[test]
317 fn test_io_orchestration_classification() {
318 let graph = CallGraph::new();
319
320 let mut func = create_test_metrics("output_unified_priorities", 12, 19, 38);
326 func.nesting = 3;
327
328 let func_id = FunctionId::new(
329 PathBuf::from("main.rs"),
330 "output_unified_priorities".to_string(),
331 861,
332 );
333
334 let role = classify_function_role(&func, &func_id, &graph);
335 assert_eq!(role, FunctionRole::IOWrapper);
336
337 func.nesting = 4;
339 let role = classify_function_role(&func, &func_id, &graph);
340 assert_eq!(role, FunctionRole::PureLogic);
341 }
342
343 #[test]
344 fn test_pure_logic_classification() {
345 let graph = CallGraph::new();
346 let func = create_test_metrics("calculate_risk", 8, 12, 60);
347 let func_id = FunctionId::new(PathBuf::from("calc.rs"), "calculate_risk".to_string(), 20);
348
349 let role = classify_function_role(&func, &func_id, &graph);
350 assert_eq!(role, FunctionRole::PureLogic);
351 }
352
353 #[test]
354 fn test_role_multipliers() {
355 assert_eq!(get_role_multiplier(FunctionRole::PureLogic), 0.7);
357 assert_eq!(get_role_multiplier(FunctionRole::Orchestrator), 0.8);
358 assert_eq!(get_role_multiplier(FunctionRole::IOWrapper), 0.7);
359 assert_eq!(get_role_multiplier(FunctionRole::EntryPoint), 0.9);
360 assert_eq!(get_role_multiplier(FunctionRole::PatternMatch), 0.6);
361 assert_eq!(get_role_multiplier(FunctionRole::Unknown), 1.0);
362 }
363
364 #[test]
365 fn test_formatting_function_not_orchestrator() {
366 let mut graph = CallGraph::new();
367
368 let func = create_test_metrics("format_recommendation_box_header", 1, 0, 9);
370 let func_id = FunctionId::new(
371 PathBuf::from("insights.rs"),
372 "format_recommendation_box_header".to_string(),
373 142,
374 );
375
376 graph.add_function(func_id.clone(), false, false, 1, 9);
378
379 let callee1 = FunctionId::new(
381 PathBuf::from("insights.rs"),
382 "calculate_dash_count".to_string(),
383 138,
384 );
385 let callee2 = FunctionId::new(PathBuf::from("std"), "format".to_string(), 1);
386
387 graph.add_function(callee1.clone(), false, false, 1, 3);
388 graph.add_function(callee2.clone(), false, false, 1, 1);
389
390 graph.add_call(FunctionCall {
391 caller: func_id.clone(),
392 callee: callee1,
393 call_type: CallType::Direct,
394 });
395 graph.add_call(FunctionCall {
396 caller: func_id.clone(),
397 callee: callee2,
398 call_type: CallType::Direct,
399 });
400
401 let role = classify_function_role(&func, &func_id, &graph);
403 assert_eq!(
404 role,
405 FunctionRole::PureLogic,
406 "Formatting function should be PureLogic, not Orchestrator"
407 );
408
409 assert!(
411 !delegates_to_tested_functions(&func_id, &graph, 0.8),
412 "Should not be considered delegation when calling std functions"
413 );
414 }
415
416 #[test]
417 fn test_actual_orchestrator_with_meaningful_callees() {
418 let mut graph = CallGraph::new();
419
420 let func = create_test_metrics("coordinate_workflow", 2, 3, 15);
422 let func_id = FunctionId::new(
423 PathBuf::from("workflow.rs"),
424 "coordinate_workflow".to_string(),
425 10,
426 );
427
428 graph.add_function(func_id.clone(), false, false, 2, 15);
429
430 let callee1 = FunctionId::new(
433 PathBuf::from("workflow.rs"),
434 "process_step_one".to_string(),
435 50,
436 );
437 let callee2 = FunctionId::new(
438 PathBuf::from("workflow.rs"),
439 "process_step_two".to_string(),
440 100,
441 );
442 let callee3 = FunctionId::new(
443 PathBuf::from("workflow.rs"),
444 "process_step_three".to_string(),
445 150,
446 );
447
448 graph.add_function(callee1.clone(), false, false, 5, 30);
449 graph.add_function(callee2.clone(), false, false, 5, 30);
450 graph.add_function(callee3.clone(), false, false, 5, 30);
451
452 graph.add_call(FunctionCall {
453 caller: func_id.clone(),
454 callee: callee1,
455 call_type: CallType::Direct,
456 });
457 graph.add_call(FunctionCall {
458 caller: func_id.clone(),
459 callee: callee2,
460 call_type: CallType::Direct,
461 });
462 graph.add_call(FunctionCall {
463 caller: func_id.clone(),
464 callee: callee3,
465 call_type: CallType::Direct,
466 });
467
468 let role = classify_function_role(&func, &func_id, &graph);
470 assert_eq!(
471 role,
472 FunctionRole::Orchestrator,
473 "Function coordinating multiple steps should be Orchestrator"
474 );
475 }
476
477 #[test]
478 fn test_orchestrator_with_error_handling_complexity_5() {
479 let mut graph = CallGraph::new();
480
481 let func = create_test_metrics("coordinate_tasks", 5, 3, 20);
483 let func_id = FunctionId::new(
484 PathBuf::from("tasks.rs"),
485 "coordinate_tasks".to_string(),
486 10,
487 );
488
489 graph.add_function(func_id.clone(), false, false, 5, 20);
490
491 for i in 0..4 {
493 let callee_id = FunctionId::new(
494 PathBuf::from("worker.rs"),
495 format!("worker_task_{}", i),
496 i * 20,
497 );
498 graph.add_function(callee_id.clone(), false, false, 8, 40);
499 graph.add_call(FunctionCall {
500 caller: func_id.clone(),
501 callee: callee_id,
502 call_type: CallType::Direct,
503 });
504 }
505
506 let role = classify_function_role(&func, &func_id, &graph);
508 assert_eq!(
509 role,
510 FunctionRole::Orchestrator,
511 "Function with complexity 5 and good delegation ratio should be Orchestrator"
512 );
513 }
514
515 #[test]
516 fn test_delegation_ratio_calculation() {
517 let func = create_test_metrics("orchestrator", 4, 2, 20);
518
519 let callees_vec: Vec<FunctionId> = (0..4)
521 .map(|i| FunctionId::new(PathBuf::from("test.rs"), format!("callee_{}", i), i * 10))
522 .collect();
523
524 let callees: Vec<&FunctionId> = callees_vec.iter().collect();
525
526 let ratio = calculate_delegation_ratio(&func, &callees);
527 assert!(
528 (ratio - 0.2).abs() < 0.01,
529 "Expected delegation ratio of 0.2, got {}",
530 ratio
531 );
532 }
533
534 #[test]
535 fn test_high_complexity_not_orchestrator() {
536 let mut graph = CallGraph::new();
537
538 let func = create_test_metrics("complex_logic", 8, 10, 30);
540 let func_id = FunctionId::new(PathBuf::from("logic.rs"), "complex_logic".to_string(), 10);
541
542 graph.add_function(func_id.clone(), false, false, 8, 30);
543
544 for i in 0..3 {
546 let callee_id =
547 FunctionId::new(PathBuf::from("worker.rs"), format!("worker_{}", i), i * 20);
548 graph.add_function(callee_id.clone(), false, false, 5, 20);
549 graph.add_call(FunctionCall {
550 caller: func_id.clone(),
551 callee: callee_id,
552 call_type: CallType::Direct,
553 });
554 }
555
556 let role = classify_function_role(&func, &func_id, &graph);
557 assert_eq!(
558 role,
559 FunctionRole::PureLogic,
560 "Function with complexity > 5 should be PureLogic, not Orchestrator"
561 );
562 }
563
564 #[test]
565 fn test_simple_constructor_detection() {
566 let func = create_test_metrics("any", 1, 0, 9);
568 assert!(
569 is_simple_constructor(&func),
570 "any() should be detected as constructor"
571 );
572
573 let func = create_test_metrics("new", 1, 0, 5);
575 assert!(
576 is_simple_constructor(&func),
577 "new() should be detected as constructor"
578 );
579
580 let func = create_test_metrics("from_config", 1, 0, 8);
582 assert!(
583 is_simple_constructor(&func),
584 "from_config() should be detected as constructor"
585 );
586
587 let func = create_test_metrics("with_defaults", 2, 1, 12);
589 assert!(
590 is_simple_constructor(&func),
591 "with_defaults() should be detected as constructor"
592 );
593
594 let func = create_test_metrics("create_complex", 8, 12, 50);
596 assert!(
597 !is_simple_constructor(&func),
598 "Complex function should NOT be detected as constructor"
599 );
600
601 let func = create_test_metrics("new_complex", 2, 2, 25);
603 assert!(
604 !is_simple_constructor(&func),
605 "Long function should NOT be detected as constructor"
606 );
607
608 let func = create_test_metrics("new_with_logic", 2, 8, 10);
610 assert!(
611 !is_simple_constructor(&func),
612 "High cognitive complexity should NOT be detected as constructor"
613 );
614 }
615
616 #[test]
617 fn test_constructor_classification_precedence() {
618 let graph = CallGraph::new();
619 let func = create_test_metrics("any", 1, 0, 9);
620 let func_id = FunctionId::new(PathBuf::from("context/rules.rs"), "any".to_string(), 52);
621
622 let role = classify_function_role(&func, &func_id, &graph);
623 assert_eq!(
624 role,
625 FunctionRole::IOWrapper,
626 "Simple constructor should be classified as IOWrapper, not PureLogic"
627 );
628 }
629
630 #[test]
631 fn test_constructor_name_patterns() {
632 assert!(is_simple_constructor(&create_test_metrics("new", 1, 0, 5)));
634 assert!(is_simple_constructor(&create_test_metrics(
635 "default", 1, 0, 5
636 )));
637 assert!(is_simple_constructor(&create_test_metrics(
638 "empty", 1, 0, 5
639 )));
640 assert!(is_simple_constructor(&create_test_metrics("zero", 1, 0, 5)));
641
642 assert!(is_simple_constructor(&create_test_metrics(
644 "from_str", 1, 0, 8
645 )));
646 assert!(is_simple_constructor(&create_test_metrics(
647 "with_capacity",
648 2,
649 1,
650 10
651 )));
652 assert!(is_simple_constructor(&create_test_metrics(
653 "create_default",
654 1,
655 0,
656 7
657 )));
658 assert!(is_simple_constructor(&create_test_metrics(
659 "make_instance",
660 1,
661 0,
662 6
663 )));
664 assert!(is_simple_constructor(&create_test_metrics(
665 "build_config",
666 2,
667 2,
668 12
669 )));
670
671 let func = create_test_metrics("calculate_score", 1, 0, 5);
673 assert!(
674 !is_simple_constructor(&func),
675 "Non-constructor name should not match"
676 );
677 }
678
679 #[test]
680 fn test_constructor_complexity_thresholds() {
681 let func = create_test_metrics("new", 2, 3, 14);
683 assert!(
684 is_simple_constructor(&func),
685 "At threshold limits should match"
686 );
687
688 let func = create_test_metrics("new", 3, 2, 10);
690 assert!(
691 !is_simple_constructor(&func),
692 "Over cyclomatic threshold should not match"
693 );
694
695 let func = create_test_metrics("new", 1, 4, 10);
697 assert!(
698 !is_simple_constructor(&func),
699 "Over cognitive threshold should not match"
700 );
701
702 let func = create_test_metrics("new", 1, 2, 15);
704 assert!(
705 !is_simple_constructor(&func),
706 "Over length threshold should not match"
707 );
708
709 let mut func = create_test_metrics("new", 1, 2, 10);
711 func.nesting = 2;
712 assert!(
713 !is_simple_constructor(&func),
714 "Over nesting threshold should not match"
715 );
716 }
717
718 #[test]
719 fn test_ast_detects_non_standard_constructor() {
720 use syn::parse_quote;
721
722 let source: syn::ItemFn = parse_quote! {
723 pub fn create_default_client() -> Self {
724 Self {
725 timeout: Duration::from_secs(30),
726 retries: 3,
727 }
728 }
729 };
730
731 let func = create_test_metrics("create_default_client", 1, 0, 5);
732
733 assert!(
735 is_constructor_enhanced(&func, Some(&source)),
736 "AST should detect non-standard constructor name"
737 );
738
739 assert!(
741 is_constructor_enhanced(&func, None),
742 "Should fallback to name-based detection"
743 );
744 }
745
746 #[test]
747 fn test_ast_detects_result_self_constructor() {
748 use syn::parse_quote;
749
750 let source: syn::ItemFn = parse_quote! {
751 pub fn try_new(value: i32) -> Result<Self, Error> {
752 if value > 0 {
753 Ok(Self { value })
754 } else {
755 Err(Error::InvalidValue)
756 }
757 }
758 };
759
760 let func = create_test_metrics("try_new", 2, 1, 8);
761
762 assert!(
764 is_constructor_enhanced(&func, Some(&source)),
765 "AST should detect Result<Self> constructor"
766 );
767 }
768
769 #[test]
770 fn test_ast_rejects_loop_in_constructor() {
771 use syn::parse_quote;
772
773 let source: syn::ItemFn = parse_quote! {
774 pub fn process_items() -> Self {
775 let mut result = Self::new();
776 for item in items {
777 result.add(item);
778 }
779 result
780 }
781 };
782
783 let func = create_test_metrics("process_items", 2, 3, 8);
784
785 assert!(
787 !is_constructor_enhanced(&func, Some(&source)),
788 "AST should reject constructors with loops"
789 );
790 }
791
792 #[test]
793 fn test_ast_fallback_when_not_returning_self() {
794 use syn::parse_quote;
795
796 let source: syn::ItemFn = parse_quote! {
797 pub fn calculate_value() -> i32 {
798 42
799 }
800 };
801
802 let func = create_test_metrics("calculate_value", 1, 0, 3);
803
804 assert!(
806 !is_constructor_enhanced(&func, Some(&source)),
807 "AST should fallback when not returning Self"
808 );
809 }
810
811 #[test]
812 fn test_enum_converter_framework_type_name() {
813 use syn::parse_quote;
814
815 let source: syn::ItemFn = parse_quote! {
816 pub fn name(&self) -> &'static str {
817 match self {
818 FrameworkType::Django => "Django",
819 FrameworkType::Flask => "Flask",
820 FrameworkType::PyQt => "PyQt",
821 }
822 }
823 };
824
825 let func = create_test_metrics("name", 3, 0, 7);
826
827 assert!(
829 is_enum_converter_enhanced(&func, &source),
830 "FrameworkType::name should be detected as enum converter"
831 );
832 }
833
834 #[test]
835 fn test_enum_converter_builtin_exception_as_str() {
836 use syn::parse_quote;
837
838 let source: syn::ItemFn = parse_quote! {
839 fn as_str(&self) -> &str {
840 match self {
841 Self::BaseException => "BaseException",
842 Self::ValueError => "ValueError",
843 Self::TypeError => "TypeError",
844 }
845 }
846 };
847
848 let func = create_test_metrics("as_str", 3, 0, 7);
849
850 assert!(
852 is_enum_converter_enhanced(&func, &source),
853 "BuiltinException::as_str should be detected as enum converter"
854 );
855 }
856
857 #[test]
858 fn test_enum_converter_with_function_calls_not_detected() {
859 use syn::parse_quote;
860
861 let source: syn::ItemFn = parse_quote! {
862 pub fn process(&self) -> String {
863 match self {
864 Variant::A => format!("A"),
865 Variant::B => format!("B"),
866 }
867 }
868 };
869
870 let func = create_test_metrics("process", 2, 1, 7);
871
872 assert!(
874 !is_enum_converter_enhanced(&func, &source),
875 "Converter with function calls should NOT be detected"
876 );
877 }
878
879 #[test]
880 fn test_enum_converter_high_cognitive_complexity_rejected() {
881 use syn::parse_quote;
882
883 let source: syn::ItemFn = parse_quote! {
884 pub fn name(&self) -> &'static str {
885 match self {
886 Type::A => "A",
887 Type::B => "B",
888 }
889 }
890 };
891
892 let func = create_test_metrics("name", 2, 5, 7); assert!(
896 !is_enum_converter_enhanced(&func, &source),
897 "High cognitive complexity should be rejected"
898 );
899 }
900
901 #[test]
902 fn test_enum_converter_classification_precedence() {
903 use syn::parse_quote;
904
905 let graph = CallGraph::new();
906
907 let source: syn::ItemFn = parse_quote! {
908 pub fn name(&self) -> &'static str {
909 match self {
910 FrameworkType::Django => "Django",
911 FrameworkType::Flask => "Flask",
912 }
913 }
914 };
915
916 let func = create_test_metrics("name", 2, 0, 7);
917 let func_id = FunctionId::new(PathBuf::from("framework.rs"), "name".to_string(), 10);
918
919 let role = classify_by_rules(&func, &func_id, &graph, Some(&source));
920 assert_eq!(
921 role,
922 Some(FunctionRole::IOWrapper),
923 "Enum converter should be classified as IOWrapper"
924 );
925 }
926
927 #[test]
928 fn test_ast_detection_can_be_disabled() {
929 use syn::parse_quote;
930
931 let source: syn::ItemFn = parse_quote! {
932 pub fn create_default_client() -> Self {
933 Self { field: 0 }
934 }
935 };
936
937 let func = create_test_metrics("create_default_client", 1, 0, 5);
938
939 assert!(
945 is_constructor_enhanced(&func, Some(&source)),
946 "Should detect with AST when enabled"
947 );
948
949 assert!(
951 is_constructor_enhanced(&func, None),
952 "Should fallback to name-based when AST unavailable"
953 );
954 }
955
956 #[test]
959 fn test_simple_field_accessor_detected() {
960 let metrics = create_test_metrics("id", 1, 0, 3);
961 assert!(
962 is_accessor_method(&metrics, None),
963 "id() should be detected as accessor"
964 );
965
966 let metrics = create_test_metrics("get_name", 1, 0, 5);
967 assert!(
968 is_accessor_method(&metrics, None),
969 "get_name() should be detected as accessor"
970 );
971 }
972
973 #[test]
974 fn test_bool_accessor_detected() {
975 let metrics = create_test_metrics("is_active", 2, 1, 8);
976 assert!(
977 is_accessor_method(&metrics, None),
978 "is_active() should be detected as accessor"
979 );
980
981 let metrics = create_test_metrics("has_permission", 2, 0, 5);
982 assert!(
983 is_accessor_method(&metrics, None),
984 "has_permission() should be detected as accessor"
985 );
986 }
987
988 #[test]
989 fn test_converter_method_detected() {
990 let metrics = create_test_metrics("as_str", 1, 0, 3);
991 assert!(
992 is_accessor_method(&metrics, None),
993 "as_str() should be detected as accessor"
994 );
995
996 let metrics = create_test_metrics("to_string", 1, 0, 4);
997 assert!(
998 is_accessor_method(&metrics, None),
999 "to_string() should be detected as accessor"
1000 );
1001 }
1002
1003 #[test]
1004 fn test_complex_method_not_detected_as_accessor() {
1005 let metrics = create_test_metrics("get_value", 5, 3, 20);
1007 assert!(
1008 !is_accessor_method(&metrics, None),
1009 "Complex method should not be detected as accessor"
1010 );
1011 }
1012
1013 #[test]
1014 fn test_business_logic_not_misclassified_as_accessor() {
1015 let metrics = create_test_metrics("calculate_total", 4, 2, 15);
1017 assert!(
1018 !is_accessor_method(&metrics, None),
1019 "Business logic should not be detected as accessor"
1020 );
1021 }
1022
1023 #[test]
1024 fn test_ast_body_validation_for_accessor() {
1025 use syn::parse_quote;
1026
1027 let code: syn::ItemFn = parse_quote! {
1028 pub fn id(&self) -> u32 {
1029 self.id
1030 }
1031 };
1032
1033 assert!(
1034 is_simple_accessor_body(&code),
1035 "Simple field access should be valid accessor body"
1036 );
1037 }
1038
1039 #[test]
1040 fn test_accessor_side_effect_rejected() {
1041 use syn::parse_quote;
1042
1043 let code: syn::ItemFn = parse_quote! {
1044 pub fn get_value(&self) -> i32 {
1045 self.counter.fetch_add(1, Ordering::SeqCst);
1046 self.value
1047 }
1048 };
1049
1050 assert!(
1051 !is_simple_accessor_body(&code),
1052 "Accessor with side effects should be rejected"
1053 );
1054 }
1055
1056 #[test]
1057 fn test_accessor_classification_integration() {
1058 let graph = CallGraph::new();
1059
1060 let func = create_test_metrics("id", 1, 0, 3);
1062 let func_id = FunctionId::new(PathBuf::from("user.rs"), "id".to_string(), 10);
1063
1064 let role = classify_function_role(&func, &func_id, &graph);
1065 assert_eq!(
1066 role,
1067 FunctionRole::IOWrapper,
1068 "Simple accessor should be classified as IOWrapper"
1069 );
1070 }
1071
1072 #[test]
1073 fn test_accessor_with_reference_return() {
1074 use syn::parse_quote;
1075
1076 let code: syn::ItemFn = parse_quote! {
1077 pub fn name(&self) -> &str {
1078 &self.name
1079 }
1080 };
1081
1082 assert!(
1083 is_simple_accessor_body(&code),
1084 "Reference to field should be valid accessor body"
1085 );
1086 }
1087
1088 #[test]
1089 fn test_accessor_with_method_call() {
1090 use syn::parse_quote;
1091
1092 let code: syn::ItemFn = parse_quote! {
1093 pub fn name(&self) -> String {
1094 self.name.clone()
1095 }
1096 };
1097
1098 assert!(
1099 is_simple_accessor_body(&code),
1100 "Field with .clone() should be valid accessor body"
1101 );
1102 }
1103
1104 #[test]
1105 fn test_accessor_single_word_patterns() {
1106 for name in &[
1108 "id", "name", "value", "kind", "type", "status", "code", "key", "index",
1109 ] {
1110 let metrics = create_test_metrics(name, 1, 0, 3);
1111 assert!(
1112 is_accessor_method(&metrics, None),
1113 "{} should be detected as accessor",
1114 name
1115 );
1116 }
1117 }
1118
1119 #[test]
1120 fn test_accessor_prefix_patterns() {
1121 for (name, expected) in &[
1123 ("get_value", true),
1124 ("is_active", true),
1125 ("has_data", true),
1126 ("can_execute", true),
1127 ("should_run", true),
1128 ("as_str", true),
1129 ("to_string", true),
1130 ("into_iter", true),
1131 ] {
1132 let metrics = create_test_metrics(name, 1, 0, 5);
1133 assert_eq!(
1134 is_accessor_method(&metrics, None),
1135 *expected,
1136 "{} accessor detection mismatch",
1137 name
1138 );
1139 }
1140 }
1141
1142 #[test]
1143 fn test_accessor_complexity_thresholds() {
1144 let metrics = create_test_metrics("get_value", 2, 1, 9);
1146 assert!(
1147 is_accessor_method(&metrics, None),
1148 "At threshold should be detected"
1149 );
1150
1151 let metrics = create_test_metrics("get_value", 3, 1, 9);
1153 assert!(
1154 !is_accessor_method(&metrics, None),
1155 "Over cyclomatic threshold should not be detected"
1156 );
1157
1158 let metrics = create_test_metrics("get_value", 2, 2, 9);
1160 assert!(
1161 !is_accessor_method(&metrics, None),
1162 "Over cognitive threshold should not be detected"
1163 );
1164
1165 let metrics = create_test_metrics("get_value", 2, 1, 10);
1167 assert!(
1168 !is_accessor_method(&metrics, None),
1169 "At or over length threshold should not be detected"
1170 );
1171 }
1172
1173 #[test]
1174 fn test_accessor_requires_immutable_self() {
1175 use syn::parse_quote;
1176
1177 let code: syn::ItemFn = parse_quote! {
1179 pub fn value(&self) -> i32 {
1180 self.value
1181 }
1182 };
1183
1184 assert!(
1185 is_simple_accessor_body(&code),
1186 "Immutable self should be valid"
1187 );
1188
1189 let code: syn::ItemFn = parse_quote! {
1191 pub fn value(&mut self) -> i32 {
1192 self.value
1193 }
1194 };
1195
1196 assert!(
1197 !is_simple_accessor_body(&code),
1198 "Mutable self should be rejected"
1199 );
1200 }
1201
1202 #[test]
1203 fn test_accessor_precedence_in_classification() {
1204 use syn::parse_quote;
1205
1206 let graph = CallGraph::new();
1207
1208 let code: syn::ItemFn = parse_quote! {
1209 pub fn id(&self) -> u32 {
1210 self.id
1211 }
1212 };
1213
1214 let func = create_test_metrics("id", 1, 0, 3);
1215 let func_id = FunctionId::new(PathBuf::from("test.rs"), "id".to_string(), 10);
1216
1217 let role = classify_by_rules(&func, &func_id, &graph, Some(&code));
1218 assert_eq!(
1219 role,
1220 Some(FunctionRole::IOWrapper),
1221 "Accessor should be classified as IOWrapper"
1222 );
1223 }
1224
1225 #[test]
1228 fn test_debug_function_with_diagnostics_suffix() {
1229 let func = create_test_metrics("handle_call_graph_diagnostics", 5, 3, 20);
1230 assert!(
1231 is_debug_function(&func),
1232 "Function with _diagnostics suffix should be detected as debug"
1233 );
1234 }
1235
1236 #[test]
1237 fn test_debug_function_with_debug_prefix() {
1238 let func = create_test_metrics("debug_print_info", 3, 2, 15);
1239 assert!(
1240 is_debug_function(&func),
1241 "Function with debug_ prefix should be detected as debug"
1242 );
1243 }
1244
1245 #[test]
1246 fn test_print_function_detected_as_debug() {
1247 let func = create_test_metrics("print_statistics", 4, 3, 18);
1248 assert!(
1249 is_debug_function(&func),
1250 "Function with print_ prefix should be detected as debug"
1251 );
1252 }
1253
1254 #[test]
1255 fn test_dump_function_detected_as_debug() {
1256 let func = create_test_metrics("dump_state", 2, 1, 10);
1257 assert!(
1258 is_debug_function(&func),
1259 "Function with dump_ prefix should be detected as debug"
1260 );
1261 }
1262
1263 #[test]
1264 fn test_trace_function_detected_as_debug() {
1265 let func = create_test_metrics("trace_execution", 3, 2, 12);
1266 assert!(
1267 is_debug_function(&func),
1268 "Function with trace_ prefix should be detected as debug"
1269 );
1270 }
1271
1272 #[test]
1273 fn test_high_complexity_debug_name_not_debug() {
1274 let func = create_test_metrics("debug_complex_algorithm", 15, 12, 50);
1276 assert!(
1277 !is_debug_function(&func),
1278 "High complexity function should not be classified as debug even with debug name"
1279 );
1280 }
1281
1282 #[test]
1283 fn test_debug_stats_function() {
1284 let func = create_test_metrics("calculate_stats", 4, 3, 20);
1285 assert!(
1286 is_debug_function(&func),
1287 "Function ending with _stats should be detected as debug"
1288 );
1289 }
1290
1291 #[test]
1292 fn test_debug_classification_integration() {
1293 let graph = CallGraph::new();
1294 let func = create_test_metrics("print_call_graph_diagnostics", 5, 3, 20);
1296 let func_id = FunctionId::new(
1297 PathBuf::from("commands/analyze.rs"),
1298 "print_call_graph_diagnostics".to_string(),
1299 396,
1300 );
1301
1302 let role = classify_function_role(&func, &func_id, &graph);
1303 assert_eq!(
1304 role,
1305 FunctionRole::Debug,
1306 "Diagnostic function should be classified as Debug role"
1307 );
1308 }
1309
1310 #[test]
1311 fn test_entry_point_takes_precedence_over_debug() {
1312 let graph = CallGraph::new();
1313 let func = create_test_metrics("handle_diagnostics", 5, 3, 20);
1315 let func_id = FunctionId::new(
1316 PathBuf::from("commands/analyze.rs"),
1317 "handle_diagnostics".to_string(),
1318 396,
1319 );
1320
1321 let role = classify_function_role(&func, &func_id, &graph);
1322 assert_eq!(
1323 role,
1324 FunctionRole::EntryPoint,
1325 "Entry point pattern should take precedence over debug pattern"
1326 );
1327 }
1328
1329 #[test]
1330 fn test_business_logic_not_misclassified_as_debug() {
1331 let func = create_test_metrics("calculate_complexity", 8, 5, 30);
1332 assert!(
1333 !is_debug_function(&func),
1334 "Business logic function should not be detected as debug"
1335 );
1336 }
1337
1338 #[test]
1339 fn test_debug_role_multiplier() {
1340 let multiplier = get_role_multiplier(FunctionRole::Debug);
1341 assert_eq!(
1342 multiplier, 0.3,
1343 "Debug role should have lowest multiplier (0.3)"
1344 );
1345 }
1346
1347 #[test]
1348 fn test_matches_debug_pattern() {
1349 assert!(matches_debug_pattern("debug_foo"));
1350 assert!(matches_debug_pattern("print_bar"));
1351 assert!(matches_debug_pattern("dump_state"));
1352 assert!(matches_debug_pattern("trace_execution"));
1353 assert!(matches_debug_pattern("foo_diagnostics"));
1354 assert!(matches_debug_pattern("bar_debug"));
1355 assert!(matches_debug_pattern("baz_stats"));
1356 assert!(matches_debug_pattern("test_diagnostics_helper"));
1357
1358 assert!(!matches_debug_pattern("calculate"));
1360 assert!(!matches_debug_pattern("process"));
1361 assert!(!matches_debug_pattern("validate"));
1362 }
1363
1364 #[test]
1365 fn test_diagnostic_characteristics() {
1366 let func = create_test_metrics("print_output", 3, 2, 15);
1368 assert!(
1369 has_diagnostic_characteristics(&func),
1370 "Simple print function should have diagnostic characteristics"
1371 );
1372
1373 let func = create_test_metrics("print_complex", 10, 8, 40);
1375 assert!(
1376 !has_diagnostic_characteristics(&func),
1377 "Complex function should not have diagnostic characteristics"
1378 );
1379
1380 let func = create_test_metrics("calculate_total", 3, 2, 15);
1382 assert!(
1383 !has_diagnostic_characteristics(&func),
1384 "Non-output function should not have diagnostic characteristics"
1385 );
1386
1387 let func = create_test_metrics("read_file", 3, 2, 15);
1389 assert!(
1390 !has_diagnostic_characteristics(&func),
1391 "Read/write operations should not have diagnostic characteristics"
1392 );
1393 }
1394}