cargo_fl/rules/
complexity.rs1use 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 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 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 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; 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), 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 _ => 0, }
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 _ => 0, }
184}