1use crate::analyzers::purity_detector::PurityDetector;
2use crate::analyzers::Analyzer;
3use crate::complexity::{
4 cognitive::calculate_cognitive_with_patterns,
5 cyclomatic::{calculate_cyclomatic, calculate_cyclomatic_adjusted},
6 if_else_analyzer::{IfElseChain, IfElseChainAnalyzer},
7 message_generator::{generate_enhanced_message, EnhancedComplexityMessage},
8 recursive_detector::{MatchLocation, RecursiveMatchDetector},
9 threshold_manager::{ComplexityThresholds, ThresholdPreset},
10};
11use crate::core::{
12 ast::{Ast, RustAst},
13 ComplexityMetrics, DebtItem, DebtType, Dependency, DependencyKind, FileMetrics,
14 FunctionMetrics, Language, Priority,
15};
16use crate::debt::async_errors::detect_async_errors;
17use crate::debt::error_context::analyze_error_context;
18use crate::debt::error_propagation::analyze_error_propagation;
19use crate::debt::error_swallowing::detect_error_swallowing;
20use crate::debt::panic_patterns::detect_panic_patterns;
21use crate::debt::patterns::{
22 find_code_smells_with_suppression, find_todos_and_fixmes_with_suppression,
23};
24use crate::debt::smells::{analyze_function_smells, analyze_module_smells};
25use crate::debt::suppression::{parse_suppression_comments, SuppressionContext};
26use crate::organization::{
27 FeatureEnvyDetector, GodObjectDetector, MagicValueDetector, MaintainabilityImpact,
28 OrganizationAntiPattern, OrganizationDetector, ParameterAnalyzer, PrimitiveObsessionDetector,
29};
30use crate::priority::call_graph::CallGraph;
31use crate::testing;
32use anyhow::Result;
33use quote::ToTokens;
34use std::path::{Path, PathBuf};
35use syn::spanned::Spanned;
36use syn::{visit::Visit, Item};
37
38pub struct RustAnalyzer {
39 complexity_threshold: u32,
40 enhanced_thresholds: ComplexityThresholds,
41 use_enhanced_detection: bool,
42}
43
44impl RustAnalyzer {
45 pub fn new() -> Self {
46 Self {
47 complexity_threshold: 10,
48 enhanced_thresholds: ComplexityThresholds::from_preset(ThresholdPreset::Balanced),
49 use_enhanced_detection: true,
50 }
51 }
52
53 pub fn with_threshold_preset(preset: ThresholdPreset) -> Self {
54 Self {
55 complexity_threshold: 10,
56 enhanced_thresholds: ComplexityThresholds::from_preset(preset),
57 use_enhanced_detection: true,
58 }
59 }
60}
61
62impl Default for RustAnalyzer {
63 fn default() -> Self {
64 Self::new()
65 }
66}
67
68impl Analyzer for RustAnalyzer {
69 fn parse(&self, content: &str, path: PathBuf) -> Result<Ast> {
70 let file = syn::parse_str::<syn::File>(content)?;
71 Ok(Ast::Rust(RustAst { file, path }))
72 }
73
74 fn analyze(&self, ast: &Ast) -> FileMetrics {
75 match ast {
76 Ast::Rust(rust_ast) => analyze_rust_file(
77 rust_ast,
78 self.complexity_threshold,
79 &self.enhanced_thresholds,
80 self.use_enhanced_detection,
81 ),
82 _ => FileMetrics {
83 path: PathBuf::new(),
84 language: Language::Rust,
85 complexity: ComplexityMetrics::default(),
86 debt_items: vec![],
87 dependencies: vec![],
88 duplications: vec![],
89 },
90 }
91 }
92
93 fn language(&self) -> Language {
94 Language::Rust
95 }
96}
97
98pub fn extract_rust_call_graph(ast: &RustAst) -> CallGraph {
99 use super::rust_call_graph::extract_call_graph;
100 extract_call_graph(&ast.file, &ast.path)
101}
102
103fn analyze_rust_file(
106 ast: &RustAst,
107 threshold: u32,
108 enhanced_thresholds: &ComplexityThresholds,
109 _use_enhanced: bool,
110) -> FileMetrics {
111 let source_content = std::fs::read_to_string(&ast.path).unwrap_or_default();
112 let mut visitor = FunctionVisitor::new(ast.path.clone(), source_content.clone());
113 visitor.file_ast = Some(ast.file.clone());
114 visitor.enhanced_thresholds = enhanced_thresholds.clone();
115 visitor.visit_file(&ast.file);
116
117 let debt_items = create_debt_items(
118 &ast.file,
119 &ast.path,
120 threshold,
121 &visitor.functions,
122 &source_content,
123 &visitor.enhanced_analysis,
124 );
125 let dependencies = extract_dependencies(&ast.file);
126
127 let functions = visitor.functions;
128 let (cyclomatic, cognitive) = functions.iter().fold((0, 0), |(cyc, cog), f| {
129 (cyc + f.cyclomatic, cog + f.cognitive)
130 });
131
132 FileMetrics {
133 path: ast.path.clone(),
134 language: Language::Rust,
135 complexity: ComplexityMetrics {
136 functions,
137 cyclomatic_complexity: cyclomatic,
138 cognitive_complexity: cognitive,
139 },
140 debt_items,
141 dependencies,
142 duplications: vec![],
143 }
144}
145
146fn create_debt_items(
147 file: &syn::File,
148 path: &std::path::Path,
149 threshold: u32,
150 functions: &[FunctionMetrics],
151 source_content: &str,
152 enhanced_analysis: &[EnhancedFunctionAnalysis],
153) -> Vec<DebtItem> {
154 let suppression_context = parse_suppression_comments(source_content, Language::Rust, path);
155
156 report_rust_unclosed_blocks(&suppression_context);
157
158 collect_all_rust_debt_items(
159 file,
160 path,
161 threshold,
162 functions,
163 source_content,
164 &suppression_context,
165 enhanced_analysis,
166 )
167}
168
169fn collect_all_rust_debt_items(
170 file: &syn::File,
171 path: &std::path::Path,
172 threshold: u32,
173 functions: &[FunctionMetrics],
174 source_content: &str,
175 suppression_context: &SuppressionContext,
176 enhanced_analysis: &[EnhancedFunctionAnalysis],
177) -> Vec<DebtItem> {
178 [
179 extract_debt_items_with_enhanced(file, path, threshold, functions, enhanced_analysis),
180 find_todos_and_fixmes_with_suppression(source_content, path, Some(suppression_context)),
181 find_code_smells_with_suppression(source_content, path, Some(suppression_context)),
182 extract_rust_module_smell_items(path, source_content, suppression_context),
183 extract_rust_function_smell_items(functions, suppression_context),
184 detect_error_swallowing(file, path, Some(suppression_context)),
185 detect_panic_patterns(file, path, Some(suppression_context)),
187 analyze_error_context(file, path, Some(suppression_context)),
188 detect_async_errors(file, path, Some(suppression_context)),
189 analyze_error_propagation(file, path, Some(suppression_context)),
190 analyze_resource_patterns(file, path),
192 analyze_organization_patterns(file, path),
193 testing::analyze_testing_patterns(file, path),
194 ]
195 .into_iter()
196 .flatten()
197 .collect()
198}
199
200fn extract_rust_module_smell_items(
201 path: &std::path::Path,
202 source_content: &str,
203 suppression_context: &SuppressionContext,
204) -> Vec<DebtItem> {
205 analyze_module_smells(path, source_content.lines().count())
206 .into_iter()
207 .map(|smell| smell.to_debt_item())
208 .filter(|item| !suppression_context.is_suppressed(item.line, &item.debt_type))
209 .collect()
210}
211
212fn extract_rust_function_smell_items(
213 functions: &[FunctionMetrics],
214 suppression_context: &SuppressionContext,
215) -> Vec<DebtItem> {
216 functions
217 .iter()
218 .flat_map(|func| analyze_function_smells(func, 0))
219 .map(|smell| smell.to_debt_item())
220 .filter(|item| !suppression_context.is_suppressed(item.line, &item.debt_type))
221 .collect()
222}
223
224fn report_rust_unclosed_blocks(suppression_context: &SuppressionContext) {
225 suppression_context
226 .unclosed_blocks
227 .iter()
228 .for_each(|unclosed| {
229 eprintln!(
230 "Warning: Unclosed suppression block in {} at line {}",
231 unclosed.file.display(),
232 unclosed.start_line
233 );
234 });
235}
236
237#[derive(Debug, Clone)]
238#[allow(dead_code)]
239struct EnhancedFunctionAnalysis {
240 function_name: String,
241 matches: Vec<MatchLocation>,
242 if_else_chains: Vec<IfElseChain>,
243 enhanced_message: Option<EnhancedComplexityMessage>,
244}
245
246#[derive(Clone)]
248struct FunctionMetadata {
249 is_test: bool,
250 visibility: Option<String>,
251 entropy_score: Option<crate::complexity::entropy_core::EntropyScore>,
252 purity_info: (Option<bool>, Option<f32>),
253}
254
255struct ComplexityMetricsData {
256 cyclomatic: u32,
257 cognitive: u32,
258}
259
260struct FunctionContext {
261 name: String,
262 file: PathBuf,
263 line: usize,
264 is_trait_method: bool,
265 in_test_module: bool,
266}
267
268struct FunctionVisitor {
269 functions: Vec<FunctionMetrics>,
270 current_file: PathBuf,
271 #[allow(dead_code)]
272 source_content: String,
273 in_test_module: bool,
274 current_function: Option<String>,
275 current_impl_type: Option<String>,
276 current_impl_is_trait: bool,
277 file_ast: Option<syn::File>,
278 enhanced_analysis: Vec<EnhancedFunctionAnalysis>,
279 enhanced_thresholds: ComplexityThresholds,
280}
281
282impl FunctionVisitor {
283 fn classify_test_file(path_str: &str) -> bool {
285 const TEST_DIR_PATTERNS: &[&str] = &[
287 "/tests/",
288 "/test/",
289 "/testing/",
290 "/mocks/",
291 "/mock/",
292 "/fixtures/",
293 "/fixture/",
294 "/test_helpers/",
295 "/test_utils/",
296 "/test_",
297 "/mock",
298 "/scenario",
299 "\\tests\\",
300 "\\test\\", ];
302
303 const TEST_FILE_SUFFIXES: &[&str] = &["_test.rs", "_tests.rs", "/tests.rs", "/test.rs"];
305
306 let has_test_dir = TEST_DIR_PATTERNS
308 .iter()
309 .any(|pattern| path_str.contains(pattern));
310
311 let has_test_suffix = TEST_FILE_SUFFIXES
313 .iter()
314 .any(|suffix| path_str.ends_with(suffix));
315
316 has_test_dir || has_test_suffix
317 }
318
319 fn new(file: PathBuf, source_content: String) -> Self {
320 let is_test_file = Self::classify_test_file(&file.to_string_lossy());
322
323 Self {
324 functions: Vec::new(),
325 current_file: file,
326 source_content,
327 in_test_module: is_test_file,
328 current_function: None,
329 current_impl_type: None,
330 current_impl_is_trait: false,
331 file_ast: None,
332 enhanced_analysis: Vec::new(),
333 enhanced_thresholds: ComplexityThresholds::from_preset(ThresholdPreset::Balanced),
334 }
335 }
336
337 fn get_line_number(&self, span: syn::__private::Span) -> usize {
338 span.start().line
340 }
341
342 fn analyze_function(
343 &mut self,
344 name: String,
345 item_fn: &syn::ItemFn,
346 line: usize,
347 is_trait_method: bool,
348 ) {
349 let metadata = Self::extract_function_metadata(&name, item_fn);
351
352 let complexity_metrics = self.calculate_complexity_metrics(&item_fn.block, item_fn);
354
355 let enhanced_analysis = Self::perform_enhanced_analysis(&item_fn.block);
357
358 let context = FunctionContext {
360 name: name.clone(),
361 file: self.current_file.clone(),
362 line,
363 is_trait_method,
364 in_test_module: self.in_test_module,
365 };
366 let metrics = Self::build_function_metrics(
367 context,
368 metadata.clone(),
369 complexity_metrics,
370 &item_fn.block,
371 item_fn,
372 );
373
374 let role = Self::classify_function_role(&name, metadata.is_test);
376
377 let analysis_result =
379 self.create_analysis_result(name.clone(), &metrics, role, enhanced_analysis);
380
381 self.enhanced_analysis.push(analysis_result);
382 self.functions.push(metrics);
383 }
384
385 fn extract_function_metadata(name: &str, item_fn: &syn::ItemFn) -> FunctionMetadata {
387 FunctionMetadata {
388 is_test: Self::is_test_function(name, item_fn),
389 visibility: Self::extract_visibility(&item_fn.vis),
390 entropy_score: Self::calculate_entropy_if_enabled(&item_fn.block),
391 purity_info: Self::detect_purity(item_fn),
392 }
393 }
394
395 fn calculate_complexity_metrics(
397 &self,
398 block: &syn::Block,
399 item_fn: &syn::ItemFn,
400 ) -> ComplexityMetricsData {
401 ComplexityMetricsData {
402 cyclomatic: self.calculate_cyclomatic_with_visitor(block, item_fn),
403 cognitive: self.calculate_cognitive_with_visitor(block, item_fn),
404 }
405 }
406
407 fn perform_enhanced_analysis(block: &syn::Block) -> (Vec<MatchLocation>, Vec<IfElseChain>) {
409 let mut match_detector = RecursiveMatchDetector::new();
410 let matches = match_detector.find_matches_in_block(block);
411
412 let mut if_else_analyzer = IfElseChainAnalyzer::new();
413 let if_else_chains = if_else_analyzer.analyze_block(block);
414
415 (matches, if_else_chains)
416 }
417
418 fn build_function_metrics(
420 context: FunctionContext,
421 metadata: FunctionMetadata,
422 complexity: ComplexityMetricsData,
423 block: &syn::Block,
424 item_fn: &syn::ItemFn,
425 ) -> FunctionMetrics {
426 FunctionMetrics {
427 name: context.name,
428 file: context.file,
429 line: context.line,
430 cyclomatic: complexity.cyclomatic,
431 cognitive: complexity.cognitive,
432 nesting: calculate_nesting(block),
433 length: count_function_lines(item_fn),
434 is_test: metadata.is_test,
435 visibility: metadata.visibility,
436 is_trait_method: context.is_trait_method,
437 in_test_module: context.in_test_module,
438 entropy_score: metadata.entropy_score,
439 is_pure: metadata.purity_info.0,
440 purity_confidence: metadata.purity_info.1,
441 }
442 }
443
444 fn classify_function_role(
446 name: &str,
447 is_test: bool,
448 ) -> crate::complexity::threshold_manager::FunctionRole {
449 use crate::complexity::threshold_manager::FunctionRole;
450
451 match () {
452 _ if is_test => FunctionRole::Test,
453 _ if name == "main" => FunctionRole::EntryPoint,
454 _ => FunctionRole::CoreLogic,
455 }
456 }
457
458 fn create_analysis_result(
460 &self,
461 name: String,
462 metrics: &FunctionMetrics,
463 role: crate::complexity::threshold_manager::FunctionRole,
464 enhanced_analysis: (Vec<MatchLocation>, Vec<IfElseChain>),
465 ) -> EnhancedFunctionAnalysis {
466 let (matches, if_else_chains) = enhanced_analysis;
467
468 let enhanced_message = if self.enhanced_thresholds.should_flag_function(metrics, role) {
469 Some(generate_enhanced_message(
470 metrics,
471 &matches,
472 &if_else_chains,
473 &self.enhanced_thresholds,
474 ))
475 } else {
476 None
477 };
478
479 EnhancedFunctionAnalysis {
480 function_name: name,
481 matches,
482 if_else_chains,
483 enhanced_message,
484 }
485 }
486
487 fn is_test_function(name: &str, item_fn: &syn::ItemFn) -> bool {
488 Self::has_test_attribute(&item_fn.attrs) || Self::has_test_name_pattern(name)
490 }
491
492 fn has_test_attribute(attrs: &[syn::Attribute]) -> bool {
493 attrs.iter().any(|attr| match () {
494 _ if attr.path().is_ident("test") => true,
495 _ if attr
496 .path()
497 .segments
498 .last()
499 .is_some_and(|seg| seg.ident == "test") =>
500 {
501 true
502 }
503 _ if attr.path().is_ident("cfg") => {
504 attr.meta.to_token_stream().to_string().contains("test")
505 }
506 _ => false,
507 })
508 }
509
510 fn has_test_name_pattern(name: &str) -> bool {
511 const TEST_PREFIXES: &[&str] = &["test_", "it_", "should_"];
512 const MOCK_PATTERNS: &[&str] = &["mock", "stub", "fake"];
513
514 let name_lower = name.to_lowercase();
515
516 match () {
517 _ if TEST_PREFIXES.iter().any(|prefix| name.starts_with(prefix)) => true,
518 _ if MOCK_PATTERNS
519 .iter()
520 .any(|pattern| name_lower.contains(pattern)) =>
521 {
522 true
523 }
524 _ => false,
525 }
526 }
527
528 fn extract_visibility(vis: &syn::Visibility) -> Option<String> {
529 match vis {
530 syn::Visibility::Public(_) => Some("pub".to_string()),
531 syn::Visibility::Restricted(restricted) => {
532 if restricted.path.is_ident("crate") {
533 Some("pub(crate)".to_string())
534 } else {
535 Some(format!("pub({})", quote::quote!(#restricted.path)))
536 }
537 }
538 syn::Visibility::Inherited => None,
539 }
540 }
541
542 fn calculate_entropy_if_enabled(
543 block: &syn::Block,
544 ) -> Option<crate::complexity::entropy_core::EntropyScore> {
545 if crate::config::get_entropy_config().enabled {
546 let mut old_analyzer = crate::complexity::entropy::EntropyAnalyzer::new();
549 let old_score = old_analyzer.calculate_entropy(block);
550
551 Some(crate::complexity::entropy_core::EntropyScore {
553 token_entropy: old_score.token_entropy,
554 pattern_repetition: old_score.pattern_repetition,
555 branch_similarity: old_score.branch_similarity,
556 effective_complexity: old_score.effective_complexity,
557 unique_variables: old_score.unique_variables,
558 max_nesting: old_score.max_nesting,
559 dampening_applied: old_score.dampening_applied,
560 })
561 } else {
562 None
563 }
564 }
565
566 fn detect_purity(item_fn: &syn::ItemFn) -> (Option<bool>, Option<f32>) {
567 let mut detector = PurityDetector::new();
568 let analysis = detector.is_pure_function(item_fn);
569 (Some(analysis.is_pure), Some(analysis.confidence))
570 }
571
572 fn calculate_cyclomatic_with_visitor(&self, block: &syn::Block, func: &syn::ItemFn) -> u32 {
573 use crate::complexity::visitor_detector::detect_visitor_pattern;
574
575 if let Some(ref file_ast) = self.file_ast {
577 if let Some(pattern_info) = detect_visitor_pattern(file_ast, func) {
578 return pattern_info.adjusted_complexity;
580 }
581 }
582
583 calculate_cyclomatic_adjusted(block)
585 }
586
587 fn calculate_cognitive_with_visitor(&self, block: &syn::Block, func: &syn::ItemFn) -> u32 {
588 use crate::complexity::visitor_detector::detect_visitor_pattern;
589
590 if let Some(ref file_ast) = self.file_ast {
592 if let Some(pattern_info) = detect_visitor_pattern(file_ast, func) {
593 let base_cognitive = calculate_cognitive_syn(block);
595 match pattern_info.pattern_type {
597 crate::complexity::visitor_detector::PatternType::Visitor => {
598 ((base_cognitive as f32).log2().ceil()).max(1.0) as u32
599 }
600 crate::complexity::visitor_detector::PatternType::ExhaustiveMatch => {
601 ((base_cognitive as f32).sqrt().ceil()).max(2.0) as u32
602 }
603 crate::complexity::visitor_detector::PatternType::SimpleMapping => {
604 ((base_cognitive as f32) * 0.2).max(1.0) as u32
605 }
606 _ => base_cognitive,
607 }
608 } else {
609 calculate_cognitive_syn(block)
610 }
611 } else {
612 calculate_cognitive_syn(block)
613 }
614 }
615}
616
617impl<'ast> Visit<'ast> for FunctionVisitor {
618 fn visit_item_impl(&mut self, item_impl: &'ast syn::ItemImpl) {
619 let impl_type = if let syn::Type::Path(type_path) = &*item_impl.self_ty {
621 type_path
622 .path
623 .segments
624 .last()
625 .map(|seg| seg.ident.to_string())
626 } else {
627 None
628 };
629
630 let is_trait_impl = item_impl.trait_.is_some();
632
633 let prev_impl_type = self.current_impl_type.clone();
635 let prev_impl_is_trait = self.current_impl_is_trait;
636 self.current_impl_type = impl_type;
637 self.current_impl_is_trait = is_trait_impl;
638
639 syn::visit::visit_item_impl(self, item_impl);
641
642 self.current_impl_type = prev_impl_type;
644 self.current_impl_is_trait = prev_impl_is_trait;
645 }
646
647 fn visit_item_mod(&mut self, item_mod: &'ast syn::ItemMod) {
648 let is_test_mod = item_mod.attrs.iter().any(|attr| {
650 attr.path().is_ident("cfg") && attr.meta.to_token_stream().to_string().contains("test")
651 });
652
653 let was_in_test_module = self.in_test_module;
654 if is_test_mod {
655 self.in_test_module = true;
656 }
657
658 syn::visit::visit_item_mod(self, item_mod);
660
661 self.in_test_module = was_in_test_module;
663 }
664
665 fn visit_item_fn(&mut self, item_fn: &'ast syn::ItemFn) {
666 let name = item_fn.sig.ident.to_string();
667 let line = self.get_line_number(item_fn.sig.ident.span());
668 self.analyze_function(name.clone(), item_fn, line, false);
669
670 let prev_function = self.current_function.clone();
672 self.current_function = Some(name);
673
674 syn::visit::visit_item_fn(self, item_fn);
676
677 self.current_function = prev_function;
679 }
680
681 fn visit_impl_item_fn(&mut self, impl_fn: &'ast syn::ImplItemFn) {
682 let method_name = impl_fn.sig.ident.to_string();
684 let name = if let Some(ref impl_type) = self.current_impl_type {
685 format!("{impl_type}::{method_name}")
686 } else {
687 method_name.clone()
688 };
689
690 let line = self.get_line_number(impl_fn.sig.ident.span());
691
692 let vis = if self.current_impl_is_trait {
695 syn::Visibility::Public(syn::Token))
697 } else {
698 impl_fn.vis.clone()
700 };
701
702 let item_fn = syn::ItemFn {
703 attrs: impl_fn.attrs.clone(),
704 vis,
705 sig: impl_fn.sig.clone(),
706 block: Box::new(impl_fn.block.clone()),
707 };
708 self.analyze_function(name.clone(), &item_fn, line, self.current_impl_is_trait);
709
710 let prev_function = self.current_function.clone();
712 self.current_function = Some(name);
713
714 syn::visit::visit_impl_item_fn(self, impl_fn);
716
717 self.current_function = prev_function;
719 }
720
721 fn visit_expr(&mut self, expr: &'ast syn::Expr) {
722 if let syn::Expr::Closure(closure) = expr {
724 let block = match &*closure.body {
726 syn::Expr::Block(expr_block) => expr_block.block.clone(),
727 _ => {
728 syn::Block {
730 brace_token: Default::default(),
731 stmts: vec![syn::Stmt::Expr(*closure.body.clone(), None)],
732 }
733 }
734 };
735
736 let cyclomatic = calculate_cyclomatic(&block);
738 let cognitive = calculate_cognitive_syn(&block);
739 let nesting = calculate_nesting(&block);
740 let length = count_lines(&block);
741
742 if cognitive > 1 || length > 1 || cyclomatic > 1 {
747 let name = if let Some(ref parent) = self.current_function {
748 format!("{}::<closure@{}>", parent, self.functions.len())
749 } else {
750 format!("<closure@{}>", self.functions.len())
751 };
752 let line = self.get_line_number(closure.body.span());
753
754 let entropy_score = if crate::config::get_entropy_config().enabled {
756 let mut old_analyzer = crate::complexity::entropy::EntropyAnalyzer::new();
757 let old_score = old_analyzer.calculate_entropy(&block);
758
759 Some(crate::complexity::entropy_core::EntropyScore {
761 token_entropy: old_score.token_entropy,
762 pattern_repetition: old_score.pattern_repetition,
763 branch_similarity: old_score.branch_similarity,
764 effective_complexity: old_score.effective_complexity,
765 unique_variables: old_score.unique_variables,
766 max_nesting: old_score.max_nesting,
767 dampening_applied: old_score.dampening_applied,
768 })
769 } else {
770 None
771 };
772
773 let metrics = FunctionMetrics {
774 name,
775 file: self.current_file.clone(),
776 line,
777 cyclomatic,
778 cognitive,
779 nesting,
780 length,
781 is_test: self.in_test_module, visibility: None, is_trait_method: false, in_test_module: self.in_test_module,
785 entropy_score,
786 is_pure: None, purity_confidence: None,
788 };
789
790 self.functions.push(metrics);
791 }
792 }
793
794 syn::visit::visit_expr(self, expr);
796 }
797}
798
799fn calculate_cognitive_syn(block: &syn::Block) -> u32 {
800 let (total, _patterns) = calculate_cognitive_with_patterns(block);
802 total
803}
804
805fn calculate_nesting(block: &syn::Block) -> u32 {
806 struct NestingVisitor {
807 current_depth: u32,
808 max_depth: u32,
809 }
810
811 impl NestingVisitor {
812 fn visit_nested<F>(&mut self, f: F)
814 where
815 F: FnOnce(&mut Self),
816 {
817 self.current_depth += 1;
818 self.max_depth = self.max_depth.max(self.current_depth);
819 f(self);
820 self.current_depth -= 1;
821 }
822 }
823
824 impl<'ast> Visit<'ast> for NestingVisitor {
825 fn visit_expr_if(&mut self, i: &'ast syn::ExprIf) {
826 self.visit_nested(|v| syn::visit::visit_expr_if(v, i));
827 }
828
829 fn visit_expr_while(&mut self, i: &'ast syn::ExprWhile) {
830 self.visit_nested(|v| syn::visit::visit_expr_while(v, i));
831 }
832
833 fn visit_expr_for_loop(&mut self, i: &'ast syn::ExprForLoop) {
834 self.visit_nested(|v| syn::visit::visit_expr_for_loop(v, i));
835 }
836
837 fn visit_expr_loop(&mut self, i: &'ast syn::ExprLoop) {
838 self.visit_nested(|v| syn::visit::visit_expr_loop(v, i));
839 }
840
841 fn visit_expr_match(&mut self, i: &'ast syn::ExprMatch) {
842 self.current_depth += 1;
844 self.max_depth = self.max_depth.max(self.current_depth);
845
846 self.visit_expr(&i.expr);
848
849 for arm in &i.arms {
851 self.visit_arm(arm);
852 }
853
854 self.current_depth -= 1;
855 }
856
857 fn visit_arm(&mut self, i: &'ast syn::Arm) {
858 for attr in &i.attrs {
862 self.visit_attribute(attr);
863 }
864 self.visit_pat(&i.pat);
865 if let Some((_, guard)) = &i.guard {
866 self.visit_expr(guard);
867 }
868 self.visit_expr(&i.body);
869 }
870
871 fn visit_block(&mut self, block: &'ast syn::Block) {
874 syn::visit::visit_block(self, block);
876 }
877 }
878
879 let mut visitor = NestingVisitor {
880 current_depth: 0,
881 max_depth: 0,
882 };
883
884 for stmt in &block.stmts {
886 visitor.visit_stmt(stmt);
887 }
888
889 visitor.max_depth
890}
891
892fn count_lines(block: &syn::Block) -> usize {
893 let span = block.span();
895 let start_line = span.start().line;
896 let end_line = span.end().line;
897
898 if end_line >= start_line {
900 end_line - start_line + 1
901 } else {
902 1 }
904}
905
906fn count_function_lines(item_fn: &syn::ItemFn) -> usize {
907 let span = item_fn.span();
909 let start_line = span.start().line;
910 let end_line = span.end().line;
911
912 if end_line >= start_line {
914 end_line - start_line + 1
915 } else {
916 1 }
918}
919
920fn extract_debt_items_with_enhanced(
921 _file: &syn::File,
922 _path: &Path,
923 threshold: u32,
924 functions: &[FunctionMetrics],
925 enhanced_analysis: &[EnhancedFunctionAnalysis],
926) -> Vec<DebtItem> {
927 functions
928 .iter()
929 .filter(|func| func.is_complex(threshold))
930 .map(|func| create_debt_item_for_function(func, threshold, enhanced_analysis))
931 .collect()
932}
933
934fn create_debt_item_for_function(
936 func: &FunctionMetrics,
937 threshold: u32,
938 enhanced_analysis: &[EnhancedFunctionAnalysis],
939) -> DebtItem {
940 let enhanced = find_enhanced_analysis_for_function(&func.name, enhanced_analysis);
942
943 match enhanced.and_then(|a| a.enhanced_message.as_ref()) {
944 Some(enhanced_msg) => create_enhanced_debt_item(func, threshold, enhanced_msg),
945 None => create_complexity_debt_item(func, threshold),
946 }
947}
948
949fn find_enhanced_analysis_for_function<'a>(
951 function_name: &str,
952 enhanced_analysis: &'a [EnhancedFunctionAnalysis],
953) -> Option<&'a EnhancedFunctionAnalysis> {
954 enhanced_analysis
955 .iter()
956 .find(|e| e.function_name == function_name)
957}
958
959fn create_enhanced_debt_item(
961 func: &FunctionMetrics,
962 threshold: u32,
963 enhanced_msg: &EnhancedComplexityMessage,
964) -> DebtItem {
965 DebtItem {
966 id: format!("complexity-{}-{}", func.file.display(), func.line),
967 debt_type: DebtType::Complexity,
968 priority: classify_priority(func.cyclomatic, threshold),
969 file: func.file.clone(),
970 line: func.line,
971 column: None,
972 message: enhanced_msg.summary.clone(),
973 context: Some(format_enhanced_context(enhanced_msg)),
974 }
975}
976
977fn classify_priority(cyclomatic: u32, threshold: u32) -> Priority {
979 if cyclomatic > threshold * 2 {
980 Priority::High
981 } else {
982 Priority::Medium
983 }
984}
985
986fn format_enhanced_context(msg: &EnhancedComplexityMessage) -> String {
987 let mut context = String::new();
988
989 if !msg.details.is_empty() {
991 context.push_str("\n\nComplexity Issues:");
992 for detail in &msg.details {
993 context.push_str(&format!("\n • {}", detail.description));
994 }
995 }
996
997 if !msg.recommendations.is_empty() {
999 context.push_str("\n\nRecommendations:");
1000 for rec in &msg.recommendations {
1001 context.push_str(&format!("\n • {}: {}", rec.title, rec.description));
1002 }
1003 }
1004
1005 context
1006}
1007
1008#[allow(dead_code)]
1009fn extract_debt_items(
1010 _file: &syn::File,
1011 _path: &Path,
1012 threshold: u32,
1013 functions: &[FunctionMetrics],
1014) -> Vec<DebtItem> {
1015 functions
1016 .iter()
1017 .filter(|func| func.is_complex(threshold))
1018 .map(|func| create_complexity_debt_item(func, threshold))
1019 .collect()
1020}
1021
1022fn create_complexity_debt_item(func: &FunctionMetrics, threshold: u32) -> DebtItem {
1023 DebtItem {
1024 id: format!("complexity-{}-{}", func.file.display(), func.line),
1025 debt_type: DebtType::Complexity,
1026 priority: classify_priority(func.cyclomatic, threshold),
1027 file: func.file.clone(),
1028 line: func.line,
1029 column: None,
1030 message: format!(
1031 "Function '{}' has high complexity (cyclomatic: {}, cognitive: {})",
1032 func.name, func.cyclomatic, func.cognitive
1033 ),
1034 context: None,
1035 }
1036}
1037
1038fn analyze_resource_patterns(file: &syn::File, path: &Path) -> Vec<DebtItem> {
1039 use crate::resource::{
1040 convert_resource_issue_to_debt_item, AsyncResourceDetector, DropDetector, ResourceDetector,
1041 UnboundedCollectionDetector,
1042 };
1043
1044 let detectors: Vec<Box<dyn ResourceDetector>> = vec![
1045 Box::new(DropDetector::new()),
1046 Box::new(AsyncResourceDetector::new()),
1047 Box::new(UnboundedCollectionDetector::new()),
1048 ];
1049
1050 let mut resource_items = Vec::new();
1051
1052 for detector in detectors {
1053 let issues = detector.detect_issues(file, path);
1054
1055 for issue in issues {
1056 let impact = detector.assess_resource_impact(&issue);
1057 let debt_item = convert_resource_issue_to_debt_item(issue, impact, path);
1058 resource_items.push(debt_item);
1059 }
1060 }
1061
1062 resource_items
1063}
1064
1065fn analyze_organization_patterns(file: &syn::File, path: &Path) -> Vec<DebtItem> {
1066 let detectors: Vec<Box<dyn OrganizationDetector>> = vec![
1067 Box::new(GodObjectDetector::new()),
1068 Box::new(MagicValueDetector::new()),
1069 Box::new(ParameterAnalyzer::new()),
1070 Box::new(FeatureEnvyDetector::new()),
1071 Box::new(PrimitiveObsessionDetector::new()),
1072 ];
1073
1074 let mut organization_items = Vec::new();
1075
1076 for detector in detectors {
1077 let anti_patterns = detector.detect_anti_patterns(file);
1078
1079 for pattern in anti_patterns {
1080 let impact = detector.estimate_maintainability_impact(&pattern);
1081 let debt_item = convert_organization_pattern_to_debt_item(pattern, impact, path);
1082 organization_items.push(debt_item);
1083 }
1084 }
1085
1086 organization_items
1087}
1088
1089fn impact_to_priority(impact: MaintainabilityImpact) -> Priority {
1091 match impact {
1092 MaintainabilityImpact::Critical => Priority::Critical,
1093 MaintainabilityImpact::High => Priority::High,
1094 MaintainabilityImpact::Medium => Priority::Medium,
1095 MaintainabilityImpact::Low => Priority::Low,
1096 }
1097}
1098
1099fn pattern_to_message_context(pattern: &OrganizationAntiPattern) -> (String, Option<String>) {
1101 match pattern {
1102 OrganizationAntiPattern::GodObject {
1103 type_name,
1104 method_count,
1105 field_count,
1106 suggested_split,
1107 ..
1108 } => (
1109 format!(
1110 "God object '{}' with {} methods and {} fields",
1111 type_name, method_count, field_count
1112 ),
1113 Some(format!(
1114 "Consider splitting into: {}",
1115 suggested_split
1116 .iter()
1117 .map(|g| g.name.as_str())
1118 .collect::<Vec<_>>()
1119 .join(", ")
1120 )),
1121 ),
1122 OrganizationAntiPattern::MagicValue {
1123 value,
1124 occurrence_count,
1125 suggested_constant_name,
1126 ..
1127 } => (
1128 format!("Magic value '{}' appears {} times", value, occurrence_count),
1129 Some(format!(
1130 "Extract constant: const {} = {};",
1131 suggested_constant_name, value
1132 )),
1133 ),
1134 OrganizationAntiPattern::LongParameterList {
1135 function_name,
1136 parameter_count,
1137 suggested_refactoring,
1138 ..
1139 } => (
1140 format!(
1141 "Function '{}' has {} parameters",
1142 function_name, parameter_count
1143 ),
1144 Some(format!("Consider: {:?}", suggested_refactoring)),
1145 ),
1146 OrganizationAntiPattern::FeatureEnvy {
1147 method_name,
1148 envied_type,
1149 external_calls,
1150 internal_calls,
1151 ..
1152 } => (
1153 format!(
1154 "Method '{}' makes {} external calls vs {} internal calls",
1155 method_name, external_calls, internal_calls
1156 ),
1157 Some(format!("Consider moving to '{}'", envied_type)),
1158 ),
1159 OrganizationAntiPattern::PrimitiveObsession {
1160 primitive_type,
1161 usage_context,
1162 suggested_domain_type,
1163 ..
1164 } => (
1165 format!(
1166 "Primitive obsession: '{}' used for {:?}",
1167 primitive_type, usage_context
1168 ),
1169 Some(format!("Consider domain type: {}", suggested_domain_type)),
1170 ),
1171 OrganizationAntiPattern::DataClump {
1172 parameter_group,
1173 suggested_struct_name,
1174 ..
1175 } => (
1176 format!(
1177 "Data clump with {} parameters",
1178 parameter_group.parameters.len()
1179 ),
1180 Some(format!("Extract struct: {}", suggested_struct_name)),
1181 ),
1182 }
1183}
1184
1185fn convert_organization_pattern_to_debt_item(
1186 pattern: OrganizationAntiPattern,
1187 impact: MaintainabilityImpact,
1188 path: &Path,
1189) -> DebtItem {
1190 let location = pattern.primary_location().clone();
1191 let line = location.line;
1192
1193 let priority = impact_to_priority(impact);
1194 let (message, context) = pattern_to_message_context(&pattern);
1195
1196 DebtItem {
1197 id: format!("organization-{}-{}", path.display(), line),
1198 debt_type: DebtType::CodeOrganization,
1199 priority,
1200 file: path.to_path_buf(),
1201 line,
1202 column: location.column,
1203 message,
1204 context,
1205 }
1206}
1207
1208fn extract_dependencies(file: &syn::File) -> Vec<Dependency> {
1209 file.items
1210 .iter()
1211 .filter_map(|item| match item {
1212 Item::Use(use_item) => extract_use_name(&use_item.tree).map(|name| Dependency {
1213 name,
1214 kind: DependencyKind::Import,
1215 }),
1216 _ => None,
1217 })
1218 .collect()
1219}
1220
1221fn extract_use_name(tree: &syn::UseTree) -> Option<String> {
1222 match tree {
1223 syn::UseTree::Path(path) => Some(path.ident.to_string()),
1224 syn::UseTree::Name(name) => Some(name.ident.to_string()),
1225 _ => None,
1226 }
1227}
1228
1229#[cfg(test)]
1230mod tests {
1231 use super::*;
1232 use syn::parse_quote;
1233
1234 #[test]
1235 fn test_classify_test_file_with_test_directories() {
1236 assert!(FunctionVisitor::classify_test_file("src/tests/mod.rs"));
1238 assert!(FunctionVisitor::classify_test_file("src/test/utils.rs"));
1239 assert!(FunctionVisitor::classify_test_file(
1240 "src/testing/helpers.rs"
1241 ));
1242 assert!(FunctionVisitor::classify_test_file("src/mocks/data.rs"));
1243 assert!(FunctionVisitor::classify_test_file("src/mock/server.rs"));
1244 assert!(FunctionVisitor::classify_test_file(
1245 "src/fixtures/sample.rs"
1246 ));
1247 assert!(FunctionVisitor::classify_test_file("src/fixture/db.rs"));
1248 assert!(FunctionVisitor::classify_test_file(
1249 "src/test_helpers/common.rs"
1250 ));
1251 assert!(FunctionVisitor::classify_test_file(
1252 "src/test_utils/setup.rs"
1253 ));
1254 assert!(FunctionVisitor::classify_test_file(
1255 "src/test_integration.rs"
1256 ));
1257 assert!(FunctionVisitor::classify_test_file("src/mockito/client.rs"));
1258 assert!(FunctionVisitor::classify_test_file("src/scenario/basic.rs"));
1259 }
1260
1261 #[test]
1262 fn test_classify_test_file_with_test_suffixes() {
1263 assert!(FunctionVisitor::classify_test_file("src/lib_test.rs"));
1265 assert!(FunctionVisitor::classify_test_file("src/module_tests.rs"));
1266 assert!(FunctionVisitor::classify_test_file("src/tests.rs"));
1267 assert!(FunctionVisitor::classify_test_file("src/test.rs"));
1268 assert!(FunctionVisitor::classify_test_file("integration_test.rs"));
1269 assert!(FunctionVisitor::classify_test_file("unit_tests.rs"));
1270 }
1271
1272 #[test]
1273 fn test_classify_test_file_with_windows_paths() {
1274 assert!(FunctionVisitor::classify_test_file("src\\tests\\mod.rs"));
1276 assert!(FunctionVisitor::classify_test_file("src\\test\\utils.rs"));
1277 assert!(FunctionVisitor::classify_test_file(
1278 "C:\\project\\tests\\integration.rs"
1279 ));
1280 assert!(FunctionVisitor::classify_test_file(
1281 "D:\\code\\test\\unit.rs"
1282 ));
1283 }
1284
1285 #[test]
1286 fn test_classify_test_file_negative_cases() {
1287 assert!(!FunctionVisitor::classify_test_file("src/main.rs"));
1289 assert!(!FunctionVisitor::classify_test_file("src/lib.rs"));
1290 assert!(!FunctionVisitor::classify_test_file("src/analyzer.rs"));
1291 assert!(!FunctionVisitor::classify_test_file(
1292 "src/core/processor.rs"
1293 ));
1294 assert!(!FunctionVisitor::classify_test_file("src/utils/helper.rs"));
1295 assert!(!FunctionVisitor::classify_test_file("src/latest.rs"));
1296 assert!(!FunctionVisitor::classify_test_file("src/contest.rs"));
1297 assert!(!FunctionVisitor::classify_test_file("src/protest.rs"));
1298 }
1299
1300 #[test]
1301 fn test_classify_test_file_edge_cases() {
1302 assert!(FunctionVisitor::classify_test_file("/tests/"));
1304 assert!(FunctionVisitor::classify_test_file("/tests/file.rs"));
1305 assert!(FunctionVisitor::classify_test_file("path/test.rs"));
1306 assert!(FunctionVisitor::classify_test_file("path/tests.rs"));
1307 assert!(!FunctionVisitor::classify_test_file(""));
1308 assert!(!FunctionVisitor::classify_test_file("/"));
1309 assert!(FunctionVisitor::classify_test_file(
1310 "deeply/nested/tests/file.rs"
1311 ));
1312 assert!(FunctionVisitor::classify_test_file(
1313 "very/deep/path/test_utils/util.rs"
1314 ));
1315 }
1316
1317 #[test]
1318 fn test_is_test_function_with_test_attribute() {
1319 let item_fn: syn::ItemFn = parse_quote! {
1320 #[test]
1321 fn my_test() {
1322 assert_eq!(1, 1);
1323 }
1324 };
1325 assert!(FunctionVisitor::is_test_function("my_test", &item_fn));
1326 }
1327
1328 #[test]
1329 fn test_is_test_function_with_tokio_test_attribute() {
1330 let item_fn: syn::ItemFn = parse_quote! {
1331 #[tokio::test]
1332 async fn my_async_test() {
1333 assert_eq!(1, 1);
1334 }
1335 };
1336 assert!(FunctionVisitor::is_test_function("my_async_test", &item_fn));
1337 }
1338
1339 #[test]
1340 fn test_is_test_function_with_cfg_test_attribute() {
1341 let item_fn: syn::ItemFn = parse_quote! {
1342 #[cfg(test)]
1343 fn helper_function() {
1344 }
1346 };
1347 assert!(FunctionVisitor::is_test_function(
1348 "helper_function",
1349 &item_fn
1350 ));
1351 }
1352
1353 #[test]
1354 fn test_is_test_function_with_test_prefix() {
1355 let item_fn: syn::ItemFn = parse_quote! {
1356 fn test_something() {
1357 assert_eq!(1, 1);
1358 }
1359 };
1360 assert!(FunctionVisitor::is_test_function(
1361 "test_something",
1362 &item_fn
1363 ));
1364 }
1365
1366 #[test]
1367 fn test_is_test_function_with_it_prefix() {
1368 let item_fn: syn::ItemFn = parse_quote! {
1369 fn it_should_work() {
1370 assert_eq!(1, 1);
1371 }
1372 };
1373 assert!(FunctionVisitor::is_test_function(
1374 "it_should_work",
1375 &item_fn
1376 ));
1377 }
1378
1379 #[test]
1380 fn test_is_test_function_with_should_prefix() {
1381 let item_fn: syn::ItemFn = parse_quote! {
1382 fn should_handle_edge_cases() {
1383 assert_eq!(1, 1);
1384 }
1385 };
1386 assert!(FunctionVisitor::is_test_function(
1387 "should_handle_edge_cases",
1388 &item_fn
1389 ));
1390 }
1391
1392 #[test]
1393 fn test_is_test_function_regular_function() {
1394 let item_fn: syn::ItemFn = parse_quote! {
1395 fn calculate_sum(a: i32, b: i32) -> i32 {
1396 a + b
1397 }
1398 };
1399 assert!(!FunctionVisitor::is_test_function(
1400 "calculate_sum",
1401 &item_fn
1402 ));
1403 }
1404
1405 #[test]
1406 fn test_extract_visibility_public() {
1407 let vis: syn::Visibility = parse_quote! { pub };
1408 assert_eq!(
1409 FunctionVisitor::extract_visibility(&vis),
1410 Some("pub".to_string())
1411 );
1412 }
1413
1414 #[test]
1415 fn test_extract_visibility_pub_crate() {
1416 let vis: syn::Visibility = parse_quote! { pub(crate) };
1417 assert_eq!(
1418 FunctionVisitor::extract_visibility(&vis),
1419 Some("pub(crate)".to_string())
1420 );
1421 }
1422
1423 #[test]
1424 fn test_extract_visibility_pub_super() {
1425 let vis: syn::Visibility = parse_quote! { pub(super) };
1426 let result = FunctionVisitor::extract_visibility(&vis);
1427 assert!(result.is_some());
1428 assert!(result.unwrap().starts_with("pub("));
1429 }
1430
1431 #[test]
1432 fn test_extract_visibility_inherited() {
1433 let vis: syn::Visibility = parse_quote! {};
1434 assert_eq!(FunctionVisitor::extract_visibility(&vis), None);
1435 }
1436
1437 #[test]
1438 fn test_calculate_entropy_if_enabled() {
1439 let block: syn::Block = parse_quote! {{
1441 let x = 1;
1442 if x > 0 {
1443 println!("positive");
1444 } else {
1445 println!("non-positive");
1446 }
1447 }};
1448
1449 let result = FunctionVisitor::calculate_entropy_if_enabled(&block);
1451 if let Some(score) = result {
1454 assert!(score.token_entropy >= 0.0 && score.token_entropy <= 1.0);
1456 assert!(score.pattern_repetition >= 0.0 && score.pattern_repetition <= 1.0);
1457 }
1458 }
1459
1460 #[test]
1461 fn test_detect_purity_pure_function() {
1462 let item_fn: syn::ItemFn = parse_quote! {
1463 fn add(a: i32, b: i32) -> i32 {
1464 a + b
1465 }
1466 };
1467 let (is_pure, confidence) = FunctionVisitor::detect_purity(&item_fn);
1468 assert!(is_pure.is_some());
1469 assert!(confidence.is_some());
1470 if let (Some(pure), Some(conf)) = (is_pure, confidence) {
1472 if pure {
1473 assert!(conf > 0.5);
1474 }
1475 }
1476 }
1477
1478 #[test]
1479 fn test_detect_purity_impure_function() {
1480 let item_fn: syn::ItemFn = parse_quote! {
1481 fn print_value(x: i32) {
1482 println!("Value: {}", x);
1483 }
1484 };
1485 let (is_pure, confidence) = FunctionVisitor::detect_purity(&item_fn);
1486 assert!(is_pure.is_some());
1487 assert!(confidence.is_some());
1488 if let Some(pure) = is_pure {
1490 assert!(!pure);
1491 }
1492 }
1493
1494 #[test]
1495 fn test_detect_purity_mutating_function() {
1496 let item_fn: syn::ItemFn = parse_quote! {
1497 fn increment(&mut self, value: i32) {
1498 self.value += value;
1499 }
1500 };
1501 let (is_pure, confidence) = FunctionVisitor::detect_purity(&item_fn);
1502 assert!(is_pure.is_some());
1503 assert!(confidence.is_some());
1504 }
1508
1509 #[test]
1510 fn test_has_test_attribute_with_simple_test() {
1511 let code = r#"
1512 #[test]
1513 fn my_test() {}
1514 "#;
1515 let item_fn = syn::parse_str::<syn::ItemFn>(code).unwrap();
1516 assert!(FunctionVisitor::has_test_attribute(&item_fn.attrs));
1517 }
1518
1519 #[test]
1520 fn test_has_test_attribute_with_tokio_test() {
1521 let code = r#"
1522 #[tokio::test]
1523 async fn my_async_test() {}
1524 "#;
1525 let item_fn = syn::parse_str::<syn::ItemFn>(code).unwrap();
1526 assert!(FunctionVisitor::has_test_attribute(&item_fn.attrs));
1527 }
1528
1529 #[test]
1530 fn test_has_test_attribute_with_cfg_test() {
1531 let code = r#"
1532 #[cfg(test)]
1533 fn helper() {}
1534 "#;
1535 let item_fn = syn::parse_str::<syn::ItemFn>(code).unwrap();
1536 assert!(FunctionVisitor::has_test_attribute(&item_fn.attrs));
1537 }
1538
1539 #[test]
1540 fn test_has_test_attribute_without_test() {
1541 let code = r#"
1542 #[derive(Debug)]
1543 fn regular_function() {}
1544 "#;
1545 let item_fn = syn::parse_str::<syn::ItemFn>(code).unwrap();
1546 assert!(!FunctionVisitor::has_test_attribute(&item_fn.attrs));
1547 }
1548
1549 #[test]
1550 fn test_has_test_name_pattern_with_test_prefix() {
1551 assert!(FunctionVisitor::has_test_name_pattern("test_something"));
1552 assert!(FunctionVisitor::has_test_name_pattern("test_"));
1553 }
1554
1555 #[test]
1556 fn test_has_test_name_pattern_with_it_prefix() {
1557 assert!(FunctionVisitor::has_test_name_pattern("it_should_work"));
1558 assert!(FunctionVisitor::has_test_name_pattern("it_"));
1559 }
1560
1561 #[test]
1562 fn test_has_test_name_pattern_with_should_prefix() {
1563 assert!(FunctionVisitor::has_test_name_pattern(
1564 "should_do_something"
1565 ));
1566 assert!(FunctionVisitor::has_test_name_pattern("should_"));
1567 }
1568
1569 #[test]
1570 fn test_has_test_name_pattern_with_mock() {
1571 assert!(FunctionVisitor::has_test_name_pattern("mock_service"));
1572 assert!(FunctionVisitor::has_test_name_pattern("get_mock"));
1573 assert!(FunctionVisitor::has_test_name_pattern("MockBuilder"));
1574 }
1575
1576 #[test]
1577 fn test_has_test_name_pattern_with_stub() {
1578 assert!(FunctionVisitor::has_test_name_pattern("stub_response"));
1579 assert!(FunctionVisitor::has_test_name_pattern("get_stub"));
1580 assert!(FunctionVisitor::has_test_name_pattern("StubFactory"));
1581 }
1582
1583 #[test]
1584 fn test_has_test_name_pattern_with_fake() {
1585 assert!(FunctionVisitor::has_test_name_pattern("fake_data"));
1586 assert!(FunctionVisitor::has_test_name_pattern("create_fake"));
1587 assert!(FunctionVisitor::has_test_name_pattern("FakeImpl"));
1588 }
1589
1590 #[test]
1591 fn test_has_test_name_pattern_regular_name() {
1592 assert!(!FunctionVisitor::has_test_name_pattern("regular_function"));
1593 assert!(!FunctionVisitor::has_test_name_pattern("process_data"));
1594 assert!(!FunctionVisitor::has_test_name_pattern("handle_request"));
1595 }
1596}