go_brrr/metrics/
nesting.rs

1//! Nesting depth metrics for code complexity analysis.
2//!
3//! This module measures how deeply nested code structures are within functions.
4//! Deep nesting is a strong indicator of code complexity that makes code harder
5//! to understand, test, and maintain.
6//!
7//! # Nesting Constructs Tracked
8//!
9//! The analyzer tracks nesting from various language constructs:
10//!
11//! | Category          | Constructs                                      |
12//! |-------------------|-------------------------------------------------|
13//! | Control Flow      | if, for, while, switch/match, try               |
14//! | Closures/Lambdas  | lambda, arrow functions, closures               |
15//! | Callbacks         | Nested function expressions (common in JS)      |
16//! | Comprehensions    | List/dict/set comprehensions with conditions    |
17//! | Blocks            | Named blocks in Rust, defer in Go               |
18//!
19//! # Risk Levels
20//!
21//! Nesting depth is classified into risk levels:
22//!
23//! - **Good (1-3)**: Acceptable nesting, code is readable
24//! - **Acceptable (4-5)**: Consider refactoring
25//! - **Complex (6-7)**: Hard to understand, should refactor
26//! - **Severe (8+)**: Refactor immediately
27//!
28//! # Example
29//!
30//! ```ignore
31//! use go_brrr::metrics::{analyze_nesting, NestingDepthLevel};
32//!
33//! let result = analyze_nesting("./src", Some("python"), Some(5))?;
34//! for func in &result.functions {
35//!     if func.risk_level == NestingDepthLevel::Severe {
36//!         println!("Deep nesting in {}: depth {} at line {}",
37//!             func.function_name, func.max_depth, func.deepest_line);
38//!         for suggestion in &func.suggestions {
39//!             println!("  - {}", suggestion);
40//!         }
41//!     }
42//! }
43//! ```
44
45use std::collections::HashMap;
46use std::path::{Path, PathBuf};
47
48use rayon::prelude::*;
49use serde::{Deserialize, Serialize};
50use tracing::debug;
51use tree_sitter::{Node, Tree};
52
53use crate::ast::AstExtractor;
54use crate::callgraph::scanner::{ProjectScanner, ScanConfig};
55use crate::error::{Result, BrrrError};
56use crate::lang::LanguageRegistry;
57
58// =============================================================================
59// TYPES
60// =============================================================================
61
62/// Risk level classification based on nesting depth.
63///
64/// Thresholds are based on software engineering best practices and
65/// readability research.
66#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Serialize, Deserialize)]
67#[serde(rename_all = "snake_case")]
68pub enum NestingDepthLevel {
69    /// Max depth 1-3: Good, acceptable nesting
70    Good,
71    /// Max depth 4-5: Acceptable but consider simplifying
72    Acceptable,
73    /// Max depth 6-7: Complex, hard to understand
74    Complex,
75    /// Max depth 8+: Severe, refactor immediately
76    Severe,
77}
78
79impl NestingDepthLevel {
80    /// Classify nesting depth into risk level.
81    #[must_use]
82    pub fn from_depth(depth: u32) -> Self {
83        match depth {
84            0..=3 => Self::Good,
85            4..=5 => Self::Acceptable,
86            6..=7 => Self::Complex,
87            _ => Self::Severe,
88        }
89    }
90
91    /// Get human-readable description.
92    #[must_use]
93    pub const fn description(&self) -> &'static str {
94        match self {
95            Self::Good => "Good nesting depth, code is readable",
96            Self::Acceptable => "Acceptable, consider simplifying if possible",
97            Self::Complex => "Complex nesting, hard to understand",
98            Self::Severe => "Severe nesting, refactor immediately",
99        }
100    }
101
102    /// Get the color code for CLI output (ANSI).
103    #[must_use]
104    pub const fn color_code(&self) -> &'static str {
105        match self {
106            Self::Good => "\x1b[32m",       // Green
107            Self::Acceptable => "\x1b[33m", // Yellow
108            Self::Complex => "\x1b[31m",    // Red
109            Self::Severe => "\x1b[35m",     // Magenta
110        }
111    }
112
113    /// Get numeric threshold for this level.
114    #[must_use]
115    pub const fn threshold(&self) -> u32 {
116        match self {
117            Self::Good => 3,
118            Self::Acceptable => 5,
119            Self::Complex => 7,
120            Self::Severe => u32::MAX,
121        }
122    }
123}
124
125impl std::fmt::Display for NestingDepthLevel {
126    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
127        match self {
128            Self::Good => write!(f, "good"),
129            Self::Acceptable => write!(f, "acceptable"),
130            Self::Complex => write!(f, "complex"),
131            Self::Severe => write!(f, "severe"),
132        }
133    }
134}
135
136/// Type of construct that contributes to nesting.
137#[derive(Debug, Clone, PartialEq, Eq, Serialize, Deserialize)]
138#[serde(rename_all = "snake_case")]
139pub enum NestingConstruct {
140    /// if statement
141    If,
142    /// else if / elif clause
143    ElseIf,
144    /// else clause
145    Else,
146    /// for loop
147    For,
148    /// while loop
149    While,
150    /// do-while loop
151    DoWhile,
152    /// switch / match statement
153    Switch,
154    /// try block
155    Try,
156    /// catch / except clause
157    Catch,
158    /// finally clause
159    Finally,
160    /// lambda / anonymous function
161    Lambda,
162    /// closure expression
163    Closure,
164    /// callback function (nested function expression)
165    Callback,
166    /// list/dict/set comprehension
167    Comprehension,
168    /// with statement (Python context manager)
169    With,
170    /// unsafe block (Rust)
171    Unsafe,
172    /// async block (Rust)
173    Async,
174    /// loop expression (Rust infinite loop)
175    Loop,
176    /// select statement (Go channels)
177    Select,
178    /// defer statement (Go)
179    Defer,
180    /// Named/labeled block
181    Block,
182    /// Generic nesting construct
183    Other(String),
184}
185
186impl std::fmt::Display for NestingConstruct {
187    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188        match self {
189            Self::If => write!(f, "if"),
190            Self::ElseIf => write!(f, "else if"),
191            Self::Else => write!(f, "else"),
192            Self::For => write!(f, "for"),
193            Self::While => write!(f, "while"),
194            Self::DoWhile => write!(f, "do-while"),
195            Self::Switch => write!(f, "switch/match"),
196            Self::Try => write!(f, "try"),
197            Self::Catch => write!(f, "catch"),
198            Self::Finally => write!(f, "finally"),
199            Self::Lambda => write!(f, "lambda"),
200            Self::Closure => write!(f, "closure"),
201            Self::Callback => write!(f, "callback"),
202            Self::Comprehension => write!(f, "comprehension"),
203            Self::With => write!(f, "with"),
204            Self::Unsafe => write!(f, "unsafe"),
205            Self::Async => write!(f, "async"),
206            Self::Loop => write!(f, "loop"),
207            Self::Select => write!(f, "select"),
208            Self::Defer => write!(f, "defer"),
209            Self::Block => write!(f, "block"),
210            Self::Other(name) => write!(f, "{}", name),
211        }
212    }
213}
214
215/// Information about a deeply nested location.
216#[derive(Debug, Clone, Serialize, Deserialize)]
217pub struct DeepNesting {
218    /// Line number where deep nesting occurs
219    pub line: usize,
220    /// Nesting depth at this location
221    pub depth: u32,
222    /// Stack of constructs leading to this depth (e.g., ["if", "for", "if", "try"])
223    pub construct_stack: Vec<String>,
224}
225
226/// Nesting metrics for a single function.
227#[derive(Debug, Clone, Serialize, Deserialize)]
228pub struct NestingMetrics {
229    /// Function name (may include class prefix for methods)
230    pub function_name: String,
231    /// File path containing the function
232    pub file: PathBuf,
233    /// Starting line number (1-indexed)
234    pub line: usize,
235    /// Ending line number (1-indexed)
236    pub end_line: usize,
237    /// Maximum nesting depth in the function
238    pub max_depth: u32,
239    /// Average nesting depth across all lines
240    pub average_depth: f64,
241    /// Line with the deepest nesting
242    pub deepest_line: usize,
243    /// All locations with deep nesting (above threshold)
244    pub deep_locations: Vec<DeepNesting>,
245    /// Risk level classification
246    pub risk_level: NestingDepthLevel,
247    /// Suggestions for reducing nesting
248    pub suggestions: Vec<String>,
249    /// Depth histogram (depth -> line count)
250    pub depth_distribution: HashMap<u32, usize>,
251}
252
253/// Simplified nesting info for summary output.
254#[derive(Debug, Clone, Serialize, Deserialize)]
255pub struct FunctionNesting {
256    /// Function name
257    pub name: String,
258    /// Starting line number
259    pub line: usize,
260    /// Maximum nesting depth
261    pub max_depth: u32,
262    /// Risk level
263    pub risk_level: NestingDepthLevel,
264}
265
266impl From<&NestingMetrics> for FunctionNesting {
267    fn from(nm: &NestingMetrics) -> Self {
268        Self {
269            name: nm.function_name.clone(),
270            line: nm.line,
271            max_depth: nm.max_depth,
272            risk_level: nm.risk_level,
273        }
274    }
275}
276
277/// Statistics for nesting analysis.
278#[derive(Debug, Clone, Serialize, Deserialize)]
279pub struct NestingStats {
280    /// Total functions analyzed
281    pub total_functions: usize,
282    /// Average maximum depth across functions
283    pub average_max_depth: f64,
284    /// Global maximum depth found
285    pub global_max_depth: u32,
286    /// Minimum maximum depth found
287    pub min_max_depth: u32,
288    /// Median maximum depth
289    pub median_max_depth: u32,
290    /// Risk level distribution
291    pub risk_distribution: HashMap<String, usize>,
292    /// Functions with depth > 5 (common threshold)
293    pub functions_over_threshold: usize,
294}
295
296impl NestingStats {
297    /// Calculate statistics from nesting metrics.
298    fn from_metrics(results: &[NestingMetrics]) -> Self {
299        if results.is_empty() {
300            return Self {
301                total_functions: 0,
302                average_max_depth: 0.0,
303                global_max_depth: 0,
304                min_max_depth: 0,
305                median_max_depth: 0,
306                risk_distribution: HashMap::new(),
307                functions_over_threshold: 0,
308            };
309        }
310
311        let depths: Vec<u32> = results.iter().map(|r| r.max_depth).collect();
312        let total = depths.len();
313        let sum: u64 = depths.iter().map(|&d| u64::from(d)).sum();
314        let average = sum as f64 / total as f64;
315
316        let max = depths.iter().copied().max().unwrap_or(0);
317        let min = depths.iter().copied().min().unwrap_or(0);
318
319        let mut sorted = depths.clone();
320        sorted.sort_unstable();
321        let median = if total % 2 == 0 {
322            (sorted[total / 2 - 1] + sorted[total / 2]) / 2
323        } else {
324            sorted[total / 2]
325        };
326
327        let mut risk_distribution = HashMap::new();
328        for r in results {
329            *risk_distribution
330                .entry(r.risk_level.to_string())
331                .or_insert(0) += 1;
332        }
333
334        let functions_over_threshold = results.iter().filter(|r| r.max_depth > 5).count();
335
336        Self {
337            total_functions: total,
338            average_max_depth: average,
339            global_max_depth: max,
340            min_max_depth: min,
341            median_max_depth: median,
342            risk_distribution,
343            functions_over_threshold,
344        }
345    }
346}
347
348/// Complete nesting analysis result.
349#[derive(Debug, Clone, Serialize, Deserialize)]
350pub struct NestingAnalysis {
351    /// Path that was analyzed
352    pub path: PathBuf,
353    /// Language filter applied
354    pub language: Option<String>,
355    /// Individual function nesting metrics
356    pub functions: Vec<NestingMetrics>,
357    /// Functions exceeding threshold
358    #[serde(skip_serializing_if = "Option::is_none")]
359    pub violations: Option<Vec<NestingMetrics>>,
360    /// Aggregate statistics
361    pub stats: NestingStats,
362    /// Threshold used for filtering
363    #[serde(skip_serializing_if = "Option::is_none")]
364    pub threshold: Option<u32>,
365    /// Analysis errors
366    #[serde(skip_serializing_if = "Vec::is_empty")]
367    pub errors: Vec<NestingAnalysisError>,
368}
369
370/// Error during nesting analysis.
371#[derive(Debug, Clone, Serialize, Deserialize)]
372pub struct NestingAnalysisError {
373    /// File path
374    pub file: PathBuf,
375    /// Error message
376    pub message: String,
377}
378
379// =============================================================================
380// NESTING CALCULATOR
381// =============================================================================
382
383/// Calculator for nesting depth with language-specific handling.
384struct NestingCalculator<'a> {
385    language: &'a str,
386    current_depth: u32,
387    max_depth: u32,
388    deepest_line: usize,
389    /// Stack of constructs at each depth level
390    construct_stack: Vec<NestingConstruct>,
391    /// Depth at each line (line number -> depth)
392    line_depths: HashMap<usize, u32>,
393    /// Deep nesting locations found
394    deep_locations: Vec<DeepNesting>,
395    /// Threshold for recording deep locations
396    threshold: u32,
397}
398
399impl<'a> NestingCalculator<'a> {
400    fn new(language: &'a str, threshold: u32) -> Self {
401        Self {
402            language,
403            current_depth: 0,
404            max_depth: 0,
405            deepest_line: 0,
406            construct_stack: Vec::new(),
407            line_depths: HashMap::new(),
408            deep_locations: Vec::new(),
409            threshold,
410        }
411    }
412
413    /// Enter a nesting level.
414    fn enter_nesting(&mut self, construct: NestingConstruct, line: usize) {
415        self.current_depth += 1;
416        self.construct_stack.push(construct);
417
418        // Update max depth tracking
419        if self.current_depth > self.max_depth {
420            self.max_depth = self.current_depth;
421            self.deepest_line = line;
422        }
423
424        // Record depth at this line
425        self.line_depths
426            .entry(line)
427            .and_modify(|d| *d = (*d).max(self.current_depth))
428            .or_insert(self.current_depth);
429
430        // Record deep location if above threshold
431        if self.current_depth > self.threshold {
432            let stack: Vec<String> = self
433                .construct_stack
434                .iter()
435                .map(|c| c.to_string())
436                .collect();
437
438            self.deep_locations.push(DeepNesting {
439                line,
440                depth: self.current_depth,
441                construct_stack: stack,
442            });
443        }
444    }
445
446    /// Exit a nesting level.
447    fn exit_nesting(&mut self) {
448        self.current_depth = self.current_depth.saturating_sub(1);
449        self.construct_stack.pop();
450    }
451
452    /// Process a node and its children.
453    fn process_node(&mut self, node: Node) {
454        let line = node.start_position().row + 1;
455        let kind = node.kind();
456
457        // Record depth at this line
458        self.line_depths
459            .entry(line)
460            .and_modify(|d| *d = (*d).max(self.current_depth))
461            .or_insert(self.current_depth);
462
463        // Check if this node increases nesting
464        let construct = self.classify_nesting_construct(node, kind);
465
466        if let Some(c) = construct {
467            self.enter_nesting(c, line);
468            self.process_children(node);
469            self.exit_nesting();
470        } else {
471            self.process_children(node);
472        }
473    }
474
475    /// Process all children of a node.
476    fn process_children(&mut self, node: Node) {
477        let mut cursor = node.walk();
478        for child in node.children(&mut cursor) {
479            self.process_node(child);
480        }
481    }
482
483    /// Classify a node as a nesting construct (or None if it doesn't add nesting).
484    fn classify_nesting_construct(&self, node: Node, kind: &str) -> Option<NestingConstruct> {
485        match self.language {
486            "python" => self.classify_python_construct(node, kind),
487            "typescript" | "javascript" | "tsx" | "jsx" => {
488                self.classify_javascript_construct(node, kind)
489            }
490            "rust" => self.classify_rust_construct(node, kind),
491            "go" => self.classify_go_construct(node, kind),
492            "java" => self.classify_java_construct(node, kind),
493            "c" | "cpp" => self.classify_c_construct(node, kind),
494            _ => self.classify_generic_construct(kind),
495        }
496    }
497
498    /// Classify Python nesting constructs.
499    fn classify_python_construct(&self, node: Node, kind: &str) -> Option<NestingConstruct> {
500        match kind {
501            "if_statement" => Some(NestingConstruct::If),
502            "elif_clause" => Some(NestingConstruct::ElseIf),
503            "else_clause" => {
504                // Check if parent is if_statement
505                if node.parent().is_some_and(|p| p.kind() == "if_statement") {
506                    Some(NestingConstruct::Else)
507                } else {
508                    None
509                }
510            }
511            "for_statement" => Some(NestingConstruct::For),
512            "while_statement" => Some(NestingConstruct::While),
513            "try_statement" => Some(NestingConstruct::Try),
514            "except_clause" | "except_group_clause" => Some(NestingConstruct::Catch),
515            "finally_clause" => Some(NestingConstruct::Finally),
516            "with_statement" => Some(NestingConstruct::With),
517            "match_statement" => Some(NestingConstruct::Switch),
518            "lambda" => Some(NestingConstruct::Lambda),
519            "list_comprehension" | "dictionary_comprehension" | "set_comprehension"
520            | "generator_expression" => Some(NestingConstruct::Comprehension),
521            _ => None,
522        }
523    }
524
525    /// Classify JavaScript/TypeScript nesting constructs.
526    fn classify_javascript_construct(&self, node: Node, kind: &str) -> Option<NestingConstruct> {
527        match kind {
528            "if_statement" => Some(NestingConstruct::If),
529            "else_clause" => Some(NestingConstruct::Else),
530            "for_statement" | "for_in_statement" | "for_of_statement" => {
531                Some(NestingConstruct::For)
532            }
533            "while_statement" => Some(NestingConstruct::While),
534            "do_statement" => Some(NestingConstruct::DoWhile),
535            "switch_statement" => Some(NestingConstruct::Switch),
536            "try_statement" => Some(NestingConstruct::Try),
537            "catch_clause" => Some(NestingConstruct::Catch),
538            "finally_clause" => Some(NestingConstruct::Finally),
539            "arrow_function" | "function_expression" => {
540                // Check if this is a callback (nested in a call)
541                if self.is_callback(node) {
542                    Some(NestingConstruct::Callback)
543                } else {
544                    Some(NestingConstruct::Lambda)
545                }
546            }
547            _ => None,
548        }
549    }
550
551    /// Check if a function expression is a callback (nested in a call expression).
552    fn is_callback(&self, node: Node) -> bool {
553        let mut current = node.parent();
554        while let Some(parent) = current {
555            match parent.kind() {
556                "call_expression" | "method_invocation" => return true,
557                "arguments" | "argument_list" => {
558                    // Continue checking parent
559                }
560                "function_declaration"
561                | "function_definition"
562                | "method_definition"
563                | "arrow_function" => {
564                    // Reached another function boundary
565                    return false;
566                }
567                _ => {}
568            }
569            current = parent.parent();
570        }
571        false
572    }
573
574    /// Classify Rust nesting constructs.
575    fn classify_rust_construct(&self, _node: Node, kind: &str) -> Option<NestingConstruct> {
576        match kind {
577            "if_expression" | "if_let_expression" => Some(NestingConstruct::If),
578            "else_clause" => Some(NestingConstruct::Else),
579            "for_expression" => Some(NestingConstruct::For),
580            "while_expression" | "while_let_expression" => Some(NestingConstruct::While),
581            "loop_expression" => Some(NestingConstruct::Loop),
582            "match_expression" => Some(NestingConstruct::Switch),
583            "closure_expression" => Some(NestingConstruct::Closure),
584            "unsafe_block" => Some(NestingConstruct::Unsafe),
585            "async_block" => Some(NestingConstruct::Async),
586            "block" => {
587                // Named blocks add nesting
588                // Note: We skip regular blocks as they don't add logical nesting
589                None
590            }
591            _ => None,
592        }
593    }
594
595    /// Classify Go nesting constructs.
596    fn classify_go_construct(&self, _node: Node, kind: &str) -> Option<NestingConstruct> {
597        match kind {
598            "if_statement" => Some(NestingConstruct::If),
599            "for_statement" => Some(NestingConstruct::For),
600            "expression_switch_statement" | "type_switch_statement" => {
601                Some(NestingConstruct::Switch)
602            }
603            "select_statement" => Some(NestingConstruct::Select),
604            "func_literal" => Some(NestingConstruct::Closure),
605            "defer_statement" => Some(NestingConstruct::Defer),
606            _ => None,
607        }
608    }
609
610    /// Classify Java nesting constructs.
611    fn classify_java_construct(&self, _node: Node, kind: &str) -> Option<NestingConstruct> {
612        match kind {
613            "if_statement" => Some(NestingConstruct::If),
614            "for_statement" | "enhanced_for_statement" => Some(NestingConstruct::For),
615            "while_statement" => Some(NestingConstruct::While),
616            "do_statement" => Some(NestingConstruct::DoWhile),
617            "switch_expression" | "switch_statement" => Some(NestingConstruct::Switch),
618            "try_statement" => Some(NestingConstruct::Try),
619            "catch_clause" => Some(NestingConstruct::Catch),
620            "finally_clause" => Some(NestingConstruct::Finally),
621            "lambda_expression" => Some(NestingConstruct::Lambda),
622            _ => None,
623        }
624    }
625
626    /// Classify C/C++ nesting constructs.
627    fn classify_c_construct(&self, _node: Node, kind: &str) -> Option<NestingConstruct> {
628        match kind {
629            "if_statement" => Some(NestingConstruct::If),
630            "else_clause" => Some(NestingConstruct::Else),
631            "for_statement" | "for_range_loop" => Some(NestingConstruct::For),
632            "while_statement" => Some(NestingConstruct::While),
633            "do_statement" => Some(NestingConstruct::DoWhile),
634            "switch_statement" => Some(NestingConstruct::Switch),
635            "try_statement" => Some(NestingConstruct::Try),
636            "catch_clause" => Some(NestingConstruct::Catch),
637            "lambda_expression" => Some(NestingConstruct::Lambda),
638            _ => None,
639        }
640    }
641
642    /// Generic classification for unknown languages.
643    fn classify_generic_construct(&self, kind: &str) -> Option<NestingConstruct> {
644        if kind.contains("if") && !kind.contains("elif") {
645            Some(NestingConstruct::If)
646        } else if kind.contains("for") || kind.contains("while") {
647            Some(NestingConstruct::For)
648        } else if kind.contains("switch") || kind.contains("match") {
649            Some(NestingConstruct::Switch)
650        } else if kind.contains("try") {
651            Some(NestingConstruct::Try)
652        } else if kind.contains("catch") || kind.contains("except") {
653            Some(NestingConstruct::Catch)
654        } else if kind.contains("lambda") || kind.contains("closure") {
655            Some(NestingConstruct::Lambda)
656        } else {
657            None
658        }
659    }
660
661    /// Calculate average depth from line depths.
662    fn average_depth(&self) -> f64 {
663        if self.line_depths.is_empty() {
664            return 0.0;
665        }
666        let sum: u64 = self.line_depths.values().map(|&d| u64::from(d)).sum();
667        sum as f64 / self.line_depths.len() as f64
668    }
669
670    /// Build depth distribution histogram.
671    fn depth_distribution(&self) -> HashMap<u32, usize> {
672        let mut dist = HashMap::new();
673        for &depth in self.line_depths.values() {
674            *dist.entry(depth).or_insert(0) += 1;
675        }
676        dist
677    }
678}
679
680// =============================================================================
681// SUGGESTION GENERATOR
682// =============================================================================
683
684/// Generate suggestions for reducing nesting depth.
685fn generate_suggestions(metrics: &NestingMetrics, language: &str) -> Vec<String> {
686    let mut suggestions = Vec::new();
687
688    if metrics.max_depth <= 3 {
689        return suggestions;
690    }
691
692    // Check for patterns in deep locations
693    for location in &metrics.deep_locations {
694        let stack = &location.construct_stack;
695
696        // Early return suggestion for nested ifs at top level
697        if stack.first() == Some(&"if".to_string())
698            && stack.iter().filter(|&s| s == "if").count() >= 2
699        {
700            suggestions.push(format!(
701                "Consider using early return/guard clauses to reduce nesting at line {}",
702                location.line
703            ));
704        }
705
706        // Extract function for deeply nested code
707        if location.depth >= 5 {
708            suggestions.push(format!(
709                "Consider extracting nested code at line {} into a separate function",
710                location.line
711            ));
712        }
713
714        // Callback hell detection (JavaScript)
715        if (language == "javascript" || language == "typescript")
716            && stack.iter().filter(|s| *s == "callback").count() >= 2
717        {
718            suggestions.push(format!(
719                "Consider using async/await or Promises to flatten callbacks at line {}",
720                location.line
721            ));
722        }
723
724        // Loop with conditional
725        if stack.contains(&"for".to_string()) && stack.contains(&"if".to_string()) {
726            suggestions.push(format!(
727                "Consider using filter/map/reduce to simplify loop with conditions at line {}",
728                location.line
729            ));
730        }
731    }
732
733    // General suggestions based on max depth
734    if metrics.max_depth >= 6 {
735        suggestions.push(
736            "Consider applying the 'Extract Method' refactoring to reduce overall complexity"
737                .to_string(),
738        );
739    }
740
741    if metrics.max_depth >= 4 && metrics.average_depth > 2.0 {
742        suggestions.push(
743            "Consider restructuring to use polymorphism instead of nested conditionals".to_string(),
744        );
745    }
746
747    // Deduplicate suggestions
748    suggestions.sort();
749    suggestions.dedup();
750
751    suggestions
752}
753
754// =============================================================================
755// PUBLIC API
756// =============================================================================
757
758/// Analyze nesting depth for a project or directory.
759///
760/// # Arguments
761///
762/// * `path` - Path to file or directory to analyze
763/// * `language` - Optional language filter
764/// * `threshold` - Optional depth threshold for filtering (default: 5)
765///
766/// # Returns
767///
768/// Complete analysis result with individual metrics and statistics.
769pub fn analyze_nesting(
770    path: impl AsRef<Path>,
771    language: Option<&str>,
772    threshold: Option<u32>,
773) -> Result<NestingAnalysis> {
774    let path = path.as_ref();
775
776    if !path.exists() {
777        return Err(BrrrError::Io(std::io::Error::new(
778            std::io::ErrorKind::NotFound,
779            format!("Path not found: {}", path.display()),
780        )));
781    }
782
783    if path.is_file() {
784        return analyze_file_nesting(path, threshold);
785    }
786
787    // Directory analysis
788    let path_str = path
789        .to_str()
790        .ok_or_else(|| BrrrError::InvalidArgument("Invalid path encoding".to_string()))?;
791
792    let scanner = ProjectScanner::new(path_str)?;
793
794    let config = if let Some(lang) = language {
795        ScanConfig::for_language(lang)
796    } else {
797        ScanConfig::default()
798    };
799
800    let scan_result = scanner.scan_with_config(&config)?;
801
802    if scan_result.files.is_empty() {
803        return Err(BrrrError::InvalidArgument(format!(
804            "No source files found in {} (filter: {:?})",
805            path.display(),
806            language
807        )));
808    }
809
810    debug!(
811        "Analyzing {} files for nesting depth",
812        scan_result.files.len()
813    );
814
815    let depth_threshold = threshold.unwrap_or(5);
816
817    // Analyze files in parallel
818    let results: Vec<(Vec<NestingMetrics>, Vec<NestingAnalysisError>)> = scan_result
819        .files
820        .par_iter()
821        .map(|file| analyze_file_functions_nesting(file, depth_threshold))
822        .collect();
823
824    // Aggregate results
825    let mut all_functions = Vec::new();
826    let mut all_errors = Vec::new();
827
828    for (functions, errors) in results {
829        all_functions.extend(functions);
830        all_errors.extend(errors);
831    }
832
833    // Calculate statistics
834    let stats = NestingStats::from_metrics(&all_functions);
835
836    // Filter violations
837    let violations = threshold.map(|t| {
838        all_functions
839            .iter()
840            .filter(|f| f.max_depth > t)
841            .cloned()
842            .collect::<Vec<_>>()
843    });
844
845    Ok(NestingAnalysis {
846        path: path.to_path_buf(),
847        language: language.map(String::from),
848        functions: all_functions,
849        violations,
850        stats,
851        threshold,
852        errors: all_errors,
853    })
854}
855
856/// Analyze nesting depth for a single file.
857pub fn analyze_file_nesting(
858    file: impl AsRef<Path>,
859    threshold: Option<u32>,
860) -> Result<NestingAnalysis> {
861    let file = file.as_ref();
862
863    if !file.exists() {
864        return Err(BrrrError::Io(std::io::Error::new(
865            std::io::ErrorKind::NotFound,
866            format!("File not found: {}", file.display()),
867        )));
868    }
869
870    if !file.is_file() {
871        return Err(BrrrError::InvalidArgument(format!(
872            "Expected a file, got directory: {}",
873            file.display()
874        )));
875    }
876
877    let depth_threshold = threshold.unwrap_or(5);
878    let (functions, errors) = analyze_file_functions_nesting(file, depth_threshold);
879
880    let stats = NestingStats::from_metrics(&functions);
881
882    let violations = threshold.map(|t| {
883        functions
884            .iter()
885            .filter(|f| f.max_depth > t)
886            .cloned()
887            .collect::<Vec<_>>()
888    });
889
890    let registry = LanguageRegistry::global();
891    let language = registry
892        .detect_language(file)
893        .map(|l| l.name().to_string());
894
895    Ok(NestingAnalysis {
896        path: file.to_path_buf(),
897        language,
898        functions,
899        violations,
900        stats,
901        threshold,
902        errors,
903    })
904}
905
906/// Internal function to analyze all functions in a file.
907fn analyze_file_functions_nesting(
908    file: &Path,
909    threshold: u32,
910) -> (Vec<NestingMetrics>, Vec<NestingAnalysisError>) {
911    let mut results = Vec::new();
912    let mut errors = Vec::new();
913
914    // Read file content
915    let source = match std::fs::read(file) {
916        Ok(s) => s,
917        Err(e) => {
918            errors.push(NestingAnalysisError {
919                file: file.to_path_buf(),
920                message: format!("Failed to read file: {}", e),
921            });
922            return (results, errors);
923        }
924    };
925
926    // Get language
927    let registry = LanguageRegistry::global();
928    let lang = match registry.detect_language(file) {
929        Some(l) => l,
930        None => {
931            errors.push(NestingAnalysisError {
932                file: file.to_path_buf(),
933                message: "Unknown language".to_string(),
934            });
935            return (results, errors);
936        }
937    };
938
939    // Parse file
940    let mut parser = match lang.parser() {
941        Ok(p) => p,
942        Err(e) => {
943            errors.push(NestingAnalysisError {
944                file: file.to_path_buf(),
945                message: format!("Failed to create parser: {}", e),
946            });
947            return (results, errors);
948        }
949    };
950
951    let tree = match parser.parse(&source, None) {
952        Some(t) => t,
953        None => {
954            errors.push(NestingAnalysisError {
955                file: file.to_path_buf(),
956                message: "Failed to parse file".to_string(),
957            });
958            return (results, errors);
959        }
960    };
961
962    // Extract module info to get function list
963    let module = match AstExtractor::extract_file(file) {
964        Ok(m) => m,
965        Err(e) => {
966            errors.push(NestingAnalysisError {
967                file: file.to_path_buf(),
968                message: format!("Failed to extract AST: {}", e),
969            });
970            return (results, errors);
971        }
972    };
973
974    let lang_name = lang.name();
975
976    // Analyze top-level functions
977    for func in &module.functions {
978        let result = analyze_function_nesting(
979            &source,
980            &tree,
981            &func.name,
982            func.line_number,
983            func.end_line_number.unwrap_or(func.line_number),
984            file,
985            lang_name,
986            threshold,
987        );
988        results.push(result);
989    }
990
991    // Analyze class methods
992    for class in &module.classes {
993        for method in &class.methods {
994            let qualified_name = format!("{}.{}", class.name, method.name);
995            let mut result = analyze_function_nesting(
996                &source,
997                &tree,
998                &qualified_name,
999                method.line_number,
1000                method.end_line_number.unwrap_or(method.line_number),
1001                file,
1002                lang_name,
1003                threshold,
1004            );
1005            result.function_name = qualified_name;
1006            results.push(result);
1007        }
1008
1009        // Analyze nested classes recursively
1010        analyze_nested_classes_nesting(
1011            &source,
1012            &tree,
1013            class,
1014            &class.name,
1015            file,
1016            lang_name,
1017            threshold,
1018            &mut results,
1019        );
1020    }
1021
1022    (results, errors)
1023}
1024
1025/// Recursively analyze nested class methods.
1026fn analyze_nested_classes_nesting(
1027    source: &[u8],
1028    tree: &Tree,
1029    class: &crate::ast::types::ClassInfo,
1030    class_prefix: &str,
1031    file: &Path,
1032    lang_name: &str,
1033    threshold: u32,
1034    results: &mut Vec<NestingMetrics>,
1035) {
1036    for nested in &class.inner_classes {
1037        let nested_prefix = format!("{}.{}", class_prefix, nested.name);
1038
1039        for method in &nested.methods {
1040            let qualified_name = format!("{}.{}", nested_prefix, method.name);
1041            let mut result = analyze_function_nesting(
1042                source,
1043                tree,
1044                &qualified_name,
1045                method.line_number,
1046                method.end_line_number.unwrap_or(method.line_number),
1047                file,
1048                lang_name,
1049                threshold,
1050            );
1051            result.function_name = qualified_name;
1052            results.push(result);
1053        }
1054
1055        // Recurse
1056        analyze_nested_classes_nesting(
1057            source,
1058            tree,
1059            nested,
1060            &nested_prefix,
1061            file,
1062            lang_name,
1063            threshold,
1064            results,
1065        );
1066    }
1067}
1068
1069/// Analyze nesting depth for a single function.
1070fn analyze_function_nesting(
1071    source: &[u8],
1072    tree: &Tree,
1073    function_name: &str,
1074    start_line: usize,
1075    end_line: usize,
1076    file: &Path,
1077    language: &str,
1078    threshold: u32,
1079) -> NestingMetrics {
1080    // Find the function node
1081    let func_node = find_function_node(tree.root_node(), function_name, start_line, source, language);
1082
1083    if let Some(node) = func_node {
1084        let mut calculator = NestingCalculator::new(language, threshold);
1085
1086        // Find and process function body
1087        if let Some(body) = find_function_body(node, language) {
1088            calculator.process_node(body);
1089        } else {
1090            // Fallback: process all children
1091            let mut cursor = node.walk();
1092            for child in node.children(&mut cursor) {
1093                if !is_function_signature_part(child.kind(), language) {
1094                    calculator.process_node(child);
1095                }
1096            }
1097        }
1098
1099        let average = calculator.average_depth();
1100        let distribution = calculator.depth_distribution();
1101        let risk_level = NestingDepthLevel::from_depth(calculator.max_depth);
1102
1103        let mut metrics = NestingMetrics {
1104            function_name: function_name.to_string(),
1105            file: file.to_path_buf(),
1106            line: start_line,
1107            end_line,
1108            max_depth: calculator.max_depth,
1109            average_depth: average,
1110            deepest_line: calculator.deepest_line,
1111            deep_locations: calculator.deep_locations,
1112            risk_level,
1113            suggestions: Vec::new(),
1114            depth_distribution: distribution,
1115        };
1116
1117        // Generate suggestions
1118        metrics.suggestions = generate_suggestions(&metrics, language);
1119
1120        return metrics;
1121    }
1122
1123    // Fallback
1124    NestingMetrics {
1125        function_name: function_name.to_string(),
1126        file: file.to_path_buf(),
1127        line: start_line,
1128        end_line,
1129        max_depth: 0,
1130        average_depth: 0.0,
1131        deepest_line: start_line,
1132        deep_locations: Vec::new(),
1133        risk_level: NestingDepthLevel::Good,
1134        suggestions: Vec::new(),
1135        depth_distribution: HashMap::new(),
1136    }
1137}
1138
1139/// Find the body node of a function.
1140fn find_function_body<'a>(node: Node<'a>, language: &str) -> Option<Node<'a>> {
1141    let body_field = match language {
1142        "python" => "body",
1143        "typescript" | "javascript" | "tsx" | "jsx" => "body",
1144        "rust" => "body",
1145        "go" => "body",
1146        "java" => "body",
1147        "c" | "cpp" => "body",
1148        _ => "body",
1149    };
1150
1151    node.child_by_field_name(body_field)
1152}
1153
1154/// Check if a node kind is part of the function signature.
1155fn is_function_signature_part(kind: &str, _language: &str) -> bool {
1156    matches!(
1157        kind,
1158        "parameters"
1159            | "formal_parameters"
1160            | "parameter_list"
1161            | "type_parameters"
1162            | "return_type"
1163            | "type_annotation"
1164            | "type"
1165            | "decorator"
1166            | "modifiers"
1167            | "visibility_modifier"
1168            | "identifier"
1169            | "name"
1170            | "def"
1171            | "fn"
1172            | "func"
1173            | "function"
1174            | "async"
1175            | "("
1176            | ")"
1177            | "{"
1178            | "}"
1179            | ":"
1180            | "->"
1181    )
1182}
1183
1184/// Find a function node by name and line number.
1185fn find_function_node<'a>(
1186    root: Node<'a>,
1187    function_name: &str,
1188    start_line: usize,
1189    source: &[u8],
1190    language: &str,
1191) -> Option<Node<'a>> {
1192    let simple_name = function_name.rsplit('.').next().unwrap_or(function_name);
1193
1194    let function_kinds: &[&str] = match language {
1195        "python" => &["function_definition"],
1196        "typescript" | "javascript" | "tsx" | "jsx" => {
1197            &["function_declaration", "method_definition", "arrow_function"]
1198        }
1199        "rust" => &["function_item"],
1200        "go" => &["function_declaration", "method_declaration"],
1201        "java" => &["method_declaration", "constructor_declaration"],
1202        "c" | "cpp" => &["function_definition"],
1203        _ => &["function_definition", "function_declaration"],
1204    };
1205
1206    find_node_recursive(root, simple_name, start_line, source, function_kinds)
1207}
1208
1209/// Recursively find a function node.
1210fn find_node_recursive<'a>(
1211    node: Node<'a>,
1212    target_name: &str,
1213    target_line: usize,
1214    source: &[u8],
1215    function_kinds: &[&str],
1216) -> Option<Node<'a>> {
1217    let node_line = node.start_position().row + 1;
1218
1219    if function_kinds.contains(&node.kind()) && node_line == target_line {
1220        if let Some(name_node) = node.child_by_field_name("name") {
1221            let name =
1222                std::str::from_utf8(&source[name_node.start_byte()..name_node.end_byte()])
1223                    .unwrap_or("");
1224            if name == target_name {
1225                return Some(node);
1226            }
1227        }
1228    }
1229
1230    let mut cursor = node.walk();
1231    for child in node.children(&mut cursor) {
1232        if let Some(found) =
1233            find_node_recursive(child, target_name, target_line, source, function_kinds)
1234        {
1235            return Some(found);
1236        }
1237    }
1238
1239    None
1240}
1241
1242// =============================================================================
1243// TESTS
1244// =============================================================================
1245
1246#[cfg(test)]
1247mod tests {
1248    use super::*;
1249    use std::io::Write;
1250    use tempfile::NamedTempFile;
1251
1252    fn create_temp_file(content: &str, extension: &str) -> NamedTempFile {
1253        let mut file = tempfile::Builder::new()
1254            .suffix(extension)
1255            .tempfile()
1256            .expect("Failed to create temp file");
1257        file.write_all(content.as_bytes())
1258            .expect("Failed to write to temp file");
1259        file
1260    }
1261
1262    #[test]
1263    fn test_depth_level_classification() {
1264        assert_eq!(NestingDepthLevel::from_depth(0), NestingDepthLevel::Good);
1265        assert_eq!(NestingDepthLevel::from_depth(3), NestingDepthLevel::Good);
1266        assert_eq!(NestingDepthLevel::from_depth(4), NestingDepthLevel::Acceptable);
1267        assert_eq!(NestingDepthLevel::from_depth(5), NestingDepthLevel::Acceptable);
1268        assert_eq!(NestingDepthLevel::from_depth(6), NestingDepthLevel::Complex);
1269        assert_eq!(NestingDepthLevel::from_depth(7), NestingDepthLevel::Complex);
1270        assert_eq!(NestingDepthLevel::from_depth(8), NestingDepthLevel::Severe);
1271        assert_eq!(NestingDepthLevel::from_depth(100), NestingDepthLevel::Severe);
1272    }
1273
1274    #[test]
1275    fn test_simple_function_no_nesting() {
1276        let source = r#"
1277def simple():
1278    return 42
1279"#;
1280        let file = create_temp_file(source, ".py");
1281        let result = analyze_file_nesting(file.path(), None);
1282
1283        assert!(result.is_ok());
1284        let analysis = result.unwrap();
1285
1286        assert_eq!(analysis.functions.len(), 1);
1287        assert_eq!(analysis.functions[0].function_name, "simple");
1288        assert_eq!(analysis.functions[0].max_depth, 0);
1289        assert_eq!(analysis.functions[0].risk_level, NestingDepthLevel::Good);
1290    }
1291
1292    #[test]
1293    fn test_single_if_nesting() {
1294        let source = r#"
1295def with_if(x):
1296    if x > 0:
1297        return 1
1298    return 0
1299"#;
1300        let file = create_temp_file(source, ".py");
1301        let result = analyze_file_nesting(file.path(), None);
1302
1303        assert!(result.is_ok());
1304        let analysis = result.unwrap();
1305
1306        assert_eq!(analysis.functions.len(), 1);
1307        assert_eq!(analysis.functions[0].max_depth, 1);
1308    }
1309
1310    #[test]
1311    fn test_nested_constructs() {
1312        let source = r#"
1313def nested(x, items):
1314    if x > 0:
1315        for item in items:
1316            if item > 5:
1317                print(item)
1318"#;
1319        let file = create_temp_file(source, ".py");
1320        let result = analyze_file_nesting(file.path(), None);
1321
1322        assert!(result.is_ok());
1323        let analysis = result.unwrap();
1324
1325        assert_eq!(analysis.functions.len(), 1);
1326        // if -> for -> if = depth 3
1327        assert_eq!(analysis.functions[0].max_depth, 3);
1328        assert_eq!(analysis.functions[0].risk_level, NestingDepthLevel::Good);
1329    }
1330
1331    #[test]
1332    fn test_deeply_nested() {
1333        let source = r#"
1334def deeply_nested(a, b, c, d, e, f):
1335    if a:
1336        if b:
1337            if c:
1338                if d:
1339                    if e:
1340                        if f:
1341                            return True
1342    return False
1343"#;
1344        let file = create_temp_file(source, ".py");
1345        let result = analyze_file_nesting(file.path(), Some(3));
1346
1347        assert!(result.is_ok());
1348        let analysis = result.unwrap();
1349
1350        assert_eq!(analysis.functions.len(), 1);
1351        assert_eq!(analysis.functions[0].max_depth, 6);
1352        assert_eq!(analysis.functions[0].risk_level, NestingDepthLevel::Complex);
1353
1354        // Should have deep locations recorded (depth > 3)
1355        assert!(!analysis.functions[0].deep_locations.is_empty());
1356
1357        // Should have suggestions
1358        assert!(!analysis.functions[0].suggestions.is_empty());
1359    }
1360
1361    #[test]
1362    fn test_try_except_nesting() {
1363        let source = r#"
1364def with_try(x):
1365    try:
1366        if x > 0:
1367            return 1 / x
1368    except ZeroDivisionError:
1369        return 0
1370"#;
1371        let file = create_temp_file(source, ".py");
1372        let result = analyze_file_nesting(file.path(), None);
1373
1374        assert!(result.is_ok());
1375        let analysis = result.unwrap();
1376
1377        assert_eq!(analysis.functions.len(), 1);
1378        // try -> if = 2, plus catch adds one more = 2 or 3 depending on implementation
1379        assert!(analysis.functions[0].max_depth >= 2);
1380    }
1381
1382    #[test]
1383    fn test_loop_nesting() {
1384        let source = r#"
1385def nested_loops(items):
1386    for i in items:
1387        for j in items:
1388            for k in items:
1389                print(i, j, k)
1390"#;
1391        let file = create_temp_file(source, ".py");
1392        let result = analyze_file_nesting(file.path(), None);
1393
1394        assert!(result.is_ok());
1395        let analysis = result.unwrap();
1396
1397        assert_eq!(analysis.functions.len(), 1);
1398        assert_eq!(analysis.functions[0].max_depth, 3);
1399    }
1400
1401    #[test]
1402    fn test_class_methods() {
1403        let source = r#"
1404class Example:
1405    def simple(self):
1406        return 1
1407
1408    def nested(self, x):
1409        if x > 0:
1410            for i in range(x):
1411                print(i)
1412"#;
1413        let file = create_temp_file(source, ".py");
1414        let result = analyze_file_nesting(file.path(), None);
1415
1416        assert!(result.is_ok());
1417        let analysis = result.unwrap();
1418
1419        assert_eq!(analysis.functions.len(), 2);
1420
1421        let simple = analysis
1422            .functions
1423            .iter()
1424            .find(|f| f.function_name == "Example.simple");
1425        let nested = analysis
1426            .functions
1427            .iter()
1428            .find(|f| f.function_name == "Example.nested");
1429
1430        assert!(simple.is_some());
1431        assert!(nested.is_some());
1432
1433        assert_eq!(simple.unwrap().max_depth, 0);
1434        assert_eq!(nested.unwrap().max_depth, 2);
1435    }
1436
1437    #[test]
1438    fn test_threshold_filtering() {
1439        let source = r#"
1440def shallow():
1441    if True:
1442        return 1
1443
1444def deep(a, b, c, d):
1445    if a:
1446        if b:
1447            if c:
1448                if d:
1449                    return True
1450    return False
1451"#;
1452        let file = create_temp_file(source, ".py");
1453        let result = analyze_file_nesting(file.path(), Some(2));
1454
1455        assert!(result.is_ok());
1456        let analysis = result.unwrap();
1457
1458        assert!(analysis.violations.is_some());
1459        let violations = analysis.violations.unwrap();
1460
1461        // Only 'deep' should be a violation
1462        assert_eq!(violations.len(), 1);
1463        assert_eq!(violations[0].function_name, "deep");
1464    }
1465
1466    #[test]
1467    fn test_typescript_nesting() {
1468        let source = r#"
1469function nested(x: number): void {
1470    if (x > 0) {
1471        for (let i = 0; i < x; i++) {
1472            console.log(i);
1473        }
1474    }
1475}
1476"#;
1477        let file = create_temp_file(source, ".ts");
1478        let result = analyze_file_nesting(file.path(), None);
1479
1480        assert!(result.is_ok());
1481        let analysis = result.unwrap();
1482
1483        assert_eq!(analysis.functions.len(), 1);
1484        assert_eq!(analysis.functions[0].max_depth, 2);
1485    }
1486
1487    #[test]
1488    fn test_statistics() {
1489        let metrics = vec![
1490            NestingMetrics {
1491                function_name: "f1".to_string(),
1492                file: PathBuf::new(),
1493                line: 1,
1494                end_line: 5,
1495                max_depth: 2,
1496                average_depth: 1.0,
1497                deepest_line: 3,
1498                deep_locations: vec![],
1499                risk_level: NestingDepthLevel::Good,
1500                suggestions: vec![],
1501                depth_distribution: HashMap::new(),
1502            },
1503            NestingMetrics {
1504                function_name: "f2".to_string(),
1505                file: PathBuf::new(),
1506                line: 10,
1507                end_line: 20,
1508                max_depth: 6,
1509                average_depth: 3.0,
1510                deepest_line: 15,
1511                deep_locations: vec![],
1512                risk_level: NestingDepthLevel::Complex,
1513                suggestions: vec![],
1514                depth_distribution: HashMap::new(),
1515            },
1516        ];
1517
1518        let stats = NestingStats::from_metrics(&metrics);
1519
1520        assert_eq!(stats.total_functions, 2);
1521        assert_eq!(stats.global_max_depth, 6);
1522        assert_eq!(stats.min_max_depth, 2);
1523        assert_eq!(stats.functions_over_threshold, 1);
1524        assert!((stats.average_max_depth - 4.0).abs() < 0.01);
1525    }
1526
1527    #[test]
1528    fn test_nonexistent_file() {
1529        let result = analyze_file_nesting("/nonexistent/path/file.py", None);
1530        assert!(result.is_err());
1531    }
1532
1533    #[test]
1534    fn test_empty_file() {
1535        let source = "# Just a comment\n";
1536        let file = create_temp_file(source, ".py");
1537        let result = analyze_file_nesting(file.path(), None);
1538
1539        assert!(result.is_ok());
1540        let analysis = result.unwrap();
1541
1542        assert_eq!(analysis.functions.len(), 0);
1543        assert_eq!(analysis.stats.total_functions, 0);
1544    }
1545
1546    #[test]
1547    fn test_comprehension_nesting() {
1548        let source = r#"
1549def with_comprehension(items):
1550    result = [x for x in items if x > 0]
1551    return result
1552"#;
1553        let file = create_temp_file(source, ".py");
1554        let result = analyze_file_nesting(file.path(), None);
1555
1556        assert!(result.is_ok());
1557        let analysis = result.unwrap();
1558
1559        assert_eq!(analysis.functions.len(), 1);
1560        // Comprehension should add 1 level of nesting
1561        assert!(analysis.functions[0].max_depth >= 1);
1562    }
1563
1564    #[test]
1565    fn test_lambda_nesting() {
1566        let source = r#"
1567def with_lambda(items):
1568    if items:
1569        result = map(lambda x: x * 2, items)
1570        return list(result)
1571    return []
1572"#;
1573        let file = create_temp_file(source, ".py");
1574        let result = analyze_file_nesting(file.path(), None);
1575
1576        assert!(result.is_ok());
1577        let analysis = result.unwrap();
1578
1579        assert_eq!(analysis.functions.len(), 1);
1580        // if -> lambda = 2
1581        assert!(analysis.functions[0].max_depth >= 2);
1582    }
1583
1584    #[test]
1585    fn test_with_statement_nesting() {
1586        let source = r#"
1587def with_context(path):
1588    with open(path) as f:
1589        for line in f:
1590            if line.strip():
1591                print(line)
1592"#;
1593        let file = create_temp_file(source, ".py");
1594        let result = analyze_file_nesting(file.path(), None);
1595
1596        assert!(result.is_ok());
1597        let analysis = result.unwrap();
1598
1599        assert_eq!(analysis.functions.len(), 1);
1600        // with -> for -> if = 3
1601        assert_eq!(analysis.functions[0].max_depth, 3);
1602    }
1603}