debtmap/analyzers/
rust.rs

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
103// Expansion function removed - now using enhanced token parsing instead
104
105fn 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        // New enhanced error handling detectors
186        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        // Existing resource and organization analysis
191        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// Helper structs for refactored code
247#[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    /// Pure function to classify if a path represents a test file
284    fn classify_test_file(path_str: &str) -> bool {
285        // Test directory patterns
286        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\\", // Windows paths
301        ];
302
303        // Test file suffixes
304        const TEST_FILE_SUFFIXES: &[&str] = &["_test.rs", "_tests.rs", "/tests.rs", "/test.rs"];
305
306        // Check directory patterns
307        let has_test_dir = TEST_DIR_PATTERNS
308            .iter()
309            .any(|pattern| path_str.contains(pattern));
310
311        // Check file suffixes
312        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        // Check if this file is a test file based on its path
321        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        // Use proc-macro2's span-locations feature to get actual line numbers
339        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        // Extract basic function metadata
350        let metadata = Self::extract_function_metadata(&name, item_fn);
351
352        // Calculate complexity metrics
353        let complexity_metrics = self.calculate_complexity_metrics(&item_fn.block, item_fn);
354
355        // Perform enhanced complexity analysis
356        let enhanced_analysis = Self::perform_enhanced_analysis(&item_fn.block);
357
358        // Build complete metrics
359        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        // Determine function role and check if it should be flagged
375        let role = Self::classify_function_role(&name, metadata.is_test);
376
377        // Create and store analysis results
378        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    // Pure function to extract metadata
386    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    // Method to calculate complexity metrics
396    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    // Pure function for enhanced analysis
408    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    // Pure function to build metrics
419    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    // Pure function to classify function role
445    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    // Method to create analysis result
459    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        // Extract pure classification logic
489        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            // For now, use the old analyzer's EntropyScore as a bridge
547            // TODO: Once old entropy is removed, update to use new framework directly
548            let mut old_analyzer = crate::complexity::entropy::EntropyAnalyzer::new();
549            let old_score = old_analyzer.calculate_entropy(block);
550
551            // Convert old score to new score format
552            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        // Check if we have the file AST and can detect visitor patterns
576        if let Some(ref file_ast) = self.file_ast {
577            if let Some(pattern_info) = detect_visitor_pattern(file_ast, func) {
578                // Return the adjusted complexity if a visitor pattern was detected
579                return pattern_info.adjusted_complexity;
580            }
581        }
582
583        // Fall back to standard calculation
584        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        // Check if we have the file AST and can detect visitor patterns
591        if let Some(ref file_ast) = self.file_ast {
592            if let Some(pattern_info) = detect_visitor_pattern(file_ast, func) {
593                // For cognitive complexity, we also apply the reduction
594                let base_cognitive = calculate_cognitive_syn(block);
595                // Apply similar scaling for cognitive complexity
596                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        // Extract the type name from the impl block
620        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        // Check if this is a trait implementation
631        let is_trait_impl = item_impl.trait_.is_some();
632
633        // Store the current impl type and trait status
634        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        // Continue visiting the impl block
640        syn::visit::visit_item_impl(self, item_impl);
641
642        // Restore previous impl type and trait status
643        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        // Check if this is a test module (has #[cfg(test)] attribute)
649        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        // Continue visiting the module content
659        syn::visit::visit_item_mod(self, item_mod);
660
661        // Restore the previous state when leaving the module
662        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        // Track the current function for closures
671        let prev_function = self.current_function.clone();
672        self.current_function = Some(name);
673
674        // Continue visiting to find nested functions
675        syn::visit::visit_item_fn(self, item_fn);
676
677        // Restore previous function context
678        self.current_function = prev_function;
679    }
680
681    fn visit_impl_item_fn(&mut self, impl_fn: &'ast syn::ImplItemFn) {
682        // Construct the full function name including the impl type
683        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        // For trait implementations, methods inherit the trait's visibility
693        // Trait methods are effectively public (accessible through the trait)
694        let vis = if self.current_impl_is_trait {
695            // Trait methods are effectively public
696            syn::Visibility::Public(syn::Token![pub](impl_fn.sig.ident.span()))
697        } else {
698            // Use the actual visibility from impl_fn for inherent impls
699            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        // Track the current function for closures
711        let prev_function = self.current_function.clone();
712        self.current_function = Some(name);
713
714        // Continue visiting to find nested items
715        syn::visit::visit_impl_item_fn(self, impl_fn);
716
717        // Restore previous function context
718        self.current_function = prev_function;
719    }
720
721    fn visit_expr(&mut self, expr: &'ast syn::Expr) {
722        // Also count closures as functions, but only if they're non-trivial
723        if let syn::Expr::Closure(closure) = expr {
724            // Convert closure body to a block for analysis
725            let block = match &*closure.body {
726                syn::Expr::Block(expr_block) => expr_block.block.clone(),
727                _ => {
728                    // Wrap single expression in a block
729                    syn::Block {
730                        brace_token: Default::default(),
731                        stmts: vec![syn::Stmt::Expr(*closure.body.clone(), None)],
732                    }
733                }
734            };
735
736            // Calculate metrics first to determine if closure is trivial
737            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            // Only track substantial closures:
743            // - Cognitive complexity > 1 (has some logic)
744            // - OR length > 1 (multi-line)
745            // - OR cyclomatic > 1 (has branches)
746            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                // Calculate entropy score if enabled
755                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                    // Convert old score to new score format
760                    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, // Closures in test modules are test-related
782                    visibility: None,             // Closures are always private
783                    is_trait_method: false,       // Closures are not trait methods
784                    in_test_module: self.in_test_module,
785                    entropy_score,
786                    is_pure: None, // TODO: Add purity detection for closures
787                    purity_confidence: None,
788                };
789
790                self.functions.push(metrics);
791            }
792        }
793
794        // Continue visiting
795        syn::visit::visit_expr(self, expr);
796    }
797}
798
799fn calculate_cognitive_syn(block: &syn::Block) -> u32 {
800    // Use the enhanced version that includes pattern detection
801    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        // Helper function to visit nested content
813        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            // Match itself increases nesting
843            self.current_depth += 1;
844            self.max_depth = self.max_depth.max(self.current_depth);
845
846            // Visit the expression being matched
847            self.visit_expr(&i.expr);
848
849            // Visit each arm (arms will handle their own nesting)
850            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            // Don't increase nesting for match arms themselves
859            // The match already increased nesting
860            // Just visit the patterns and body
861            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        // Don't count blocks themselves as they're part of control structures
872        // Only count control flow structures
873        fn visit_block(&mut self, block: &'ast syn::Block) {
874            // Just visit the statements, don't increase depth for blocks
875            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    // Visit the block's statements directly
885    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    // Get the span of the entire block to calculate actual source lines
894    let span = block.span();
895    let start_line = span.start().line;
896    let end_line = span.end().line;
897
898    // Calculate the number of lines the function spans
899    if end_line >= start_line {
900        end_line - start_line + 1
901    } else {
902        1 // Fallback for edge cases
903    }
904}
905
906fn count_function_lines(item_fn: &syn::ItemFn) -> usize {
907    // Get the span of the entire function (from signature to end of body)
908    let span = item_fn.span();
909    let start_line = span.start().line;
910    let end_line = span.end().line;
911
912    // Calculate the number of lines the function spans
913    if end_line >= start_line {
914        end_line - start_line + 1
915    } else {
916        1 // Fallback for edge cases
917    }
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
934// Pure function to create debt item for a single function
935fn create_debt_item_for_function(
936    func: &FunctionMetrics,
937    threshold: u32,
938    enhanced_analysis: &[EnhancedFunctionAnalysis],
939) -> DebtItem {
940    // Find corresponding enhanced analysis if available
941    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
949// Pure function to find enhanced analysis for a function
950fn 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
959// Pure function to create enhanced debt item
960fn 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
977// Pure function to classify priority based on complexity
978fn 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    // Add details
990    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    // Add recommendations
998    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
1089// Pure function to convert impact to priority
1090fn 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
1099// Pure function to extract message and context from pattern
1100fn 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        // Test directory patterns
1237        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        // Test file suffixes
1264        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        // Windows path patterns
1275        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        // Non-test files
1288        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        // Edge cases
1303        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                // test helper
1345            }
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        // Test with entropy disabled (default)
1440        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        // When disabled, should return None
1450        let result = FunctionVisitor::calculate_entropy_if_enabled(&block);
1451        // The actual result depends on config, but we can test it runs without panic
1452        // If enabled, it should return an EntropyScore struct
1453        if let Some(score) = result {
1454            // EntropyScore has token_entropy field between 0.0 and 1.0
1455            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        // Pure functions should have high confidence
1471        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        // Functions with side effects should be detected as impure
1489        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        // The purity detector might not always detect mutation through self
1505        // This is a known limitation, so we just verify it returns a result
1506        // without asserting the specific value
1507    }
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}