Skip to main content

cargo_fl/rules/
complexity.rs

1use super::*;
2
3pub struct CyclomaticComplexityRule {
4    max_complexity: usize,
5}
6
7impl CyclomaticComplexityRule {
8    pub fn new(max_complexity: usize) -> Self {
9        Self { max_complexity }
10    }
11}
12
13impl Rule for CyclomaticComplexityRule {
14    fn name(&self) -> &'static str {
15        "cyclomatic-complexity"
16    }
17    
18    fn check(&self, ctx: &mut RuleContext) {
19        let mut issues_to_report = Vec::new();
20        
21        // Process functions directly from ctx to get proper line info
22        for item in &ctx.syntax_tree.items {
23            if let syn::Item::Fn(func) = item {
24                let complexity = calculate_cyclomatic_complexity(&func.block.stmts);
25                
26                if complexity > self.max_complexity {
27                    let (line, col) = ctx.line_col(func.sig.ident.span());
28                    issues_to_report.push(Issue {
29                        rule: self.name().to_string(),
30                        severity: Severity::Warning,
31                        message: format!(
32                            "Function '{}' has cyclomatic complexity of {} (max: {})",
33                            func.sig.ident,
34                            complexity,
35                            self.max_complexity
36                        ),
37                        location: Location {
38                            line,
39                            column: col,
40                            end_line: None,
41                            end_column: None,
42                        },
43                        fix: None,
44                    });
45                }
46            }
47        }
48        
49        // Report all issues
50        for issue in issues_to_report {
51            ctx.report(issue);
52        }
53    }
54}
55
56pub struct CognitiveComplexityRule {
57    max_complexity: usize,
58}
59
60impl CognitiveComplexityRule {
61    pub fn new(max_complexity: usize) -> Self {
62        Self { max_complexity }
63    }
64}
65
66impl Rule for CognitiveComplexityRule {
67    fn name(&self) -> &'static str {
68        "cognitive-complexity"
69    }
70    
71    fn check(&self, ctx: &mut RuleContext) {
72        let mut issues_to_report = Vec::new();
73        
74        for item in &ctx.syntax_tree.items {
75            if let syn::Item::Fn(func) = item {
76                let complexity = calculate_cognitive_complexity(&func.block.stmts, 0);
77                
78                if complexity > self.max_complexity {
79                    let (line, col) = ctx.line_col(func.sig.ident.span());
80                    issues_to_report.push(Issue {
81                        rule: self.name().to_string(),
82                        severity: Severity::Warning,
83                        message: format!(
84                            "Function '{}' has cognitive complexity of {} (max: {})",
85                            func.sig.ident,
86                            complexity,
87                            self.max_complexity
88                        ),
89                        location: Location {
90                            line,
91                            column: col,
92                            end_line: None,
93                            end_column: None,
94                        },
95                        fix: None,
96                    });
97                }
98            }
99        }
100        
101        // Report all issues
102        for issue in issues_to_report {
103            ctx.report(issue);
104        }
105    }
106}
107
108fn calculate_cyclomatic_complexity(stmts: &[syn::Stmt]) -> usize {
109    let mut complexity = 1; // Base complexity
110    
111    for stmt in stmts {
112        complexity += count_decision_points_stmt(stmt);
113    }
114    
115    complexity
116}
117
118fn calculate_cognitive_complexity(stmts: &[syn::Stmt], nesting_level: usize) -> usize {
119    let mut complexity = 0;
120    
121    for stmt in stmts {
122        complexity += count_cognitive_complexity_stmt(stmt, nesting_level);
123    }
124    
125    complexity
126}
127
128fn count_decision_points_stmt(stmt: &syn::Stmt) -> usize {
129    match stmt {
130        syn::Stmt::Expr(expr, _) => count_decision_points_expr(expr),
131        syn::Stmt::Local(local) => {
132            local.init.as_ref()
133                .map(|init| count_decision_points_expr(&init.expr))
134                .unwrap_or(0)
135        }
136        _ => 0,
137    }
138}
139
140fn count_decision_points_expr(expr: &syn::Expr) -> usize {
141    match expr {
142        syn::Expr::If(_) => 1,
143        syn::Expr::Match(m) => m.arms.len().saturating_sub(1), // n-1 for match arms
144        syn::Expr::While(_) | syn::Expr::ForLoop(_) | syn::Expr::Loop(_) => 1,
145        syn::Expr::Binary(bin) => {
146            match bin.op {
147                syn::BinOp::And(_) | syn::BinOp::Or(_) => 1,
148                _ => 0,
149            }
150        }
151        // Recursively check sub-expressions
152        _ => 0, // Simplified for now
153    }
154}
155
156fn count_cognitive_complexity_stmt(stmt: &syn::Stmt, nesting_level: usize) -> usize {
157    match stmt {
158        syn::Stmt::Expr(expr, _) => {
159            count_cognitive_complexity_expr(expr, nesting_level)
160        }
161        syn::Stmt::Local(local) => {
162            local.init.as_ref()
163                .map(|init| count_cognitive_complexity_expr(&init.expr, nesting_level))
164                .unwrap_or(0)
165        }
166        _ => 0,
167    }
168}
169
170fn count_cognitive_complexity_expr(expr: &syn::Expr, nesting_level: usize) -> usize {
171    match expr {
172        syn::Expr::If(_) => 1 + nesting_level,
173        syn::Expr::Match(_) => 1 + nesting_level,
174        syn::Expr::While(_) | syn::Expr::ForLoop(_) | syn::Expr::Loop(_) => 1 + nesting_level,
175        syn::Expr::Binary(bin) => {
176            match bin.op {
177                syn::BinOp::And(_) | syn::BinOp::Or(_) => 1,
178                _ => 0,
179            }
180        }
181        // Recursively check sub-expressions with increased nesting
182        _ => 0, // Simplified for now
183    }
184}