codegraph_parser_api/
complexity.rs1use serde::{Deserialize, Serialize};
7
8#[derive(Debug, Clone, Default, PartialEq, Serialize, Deserialize)]
13pub struct ComplexityMetrics {
14 pub cyclomatic_complexity: u32,
23
24 pub branches: u32,
26
27 pub loops: u32,
29
30 pub logical_operators: u32,
32
33 pub max_nesting_depth: u32,
35
36 pub exception_handlers: u32,
38
39 pub early_returns: u32,
41}
42
43impl ComplexityMetrics {
44 pub fn new() -> Self {
46 Self {
47 cyclomatic_complexity: 1,
48 ..Default::default()
49 }
50 }
51
52 pub fn calculate_cyclomatic(&mut self) {
56 self.cyclomatic_complexity =
57 1 + self.branches + self.loops + self.logical_operators + self.exception_handlers;
58 }
59
60 pub fn grade(&self) -> char {
68 match self.cyclomatic_complexity {
69 1..=5 => 'A',
70 6..=10 => 'B',
71 11..=20 => 'C',
72 21..=50 => 'D',
73 _ => 'F',
74 }
75 }
76
77 pub fn exceeds_threshold(&self, threshold: u32) -> bool {
79 self.cyclomatic_complexity > threshold
80 }
81
82 pub fn has_high_nesting(&self) -> bool {
84 self.max_nesting_depth > 4
85 }
86
87 pub fn merge_nested(&mut self, nested: &ComplexityMetrics) {
89 self.branches += nested.branches;
90 self.loops += nested.loops;
91 self.logical_operators += nested.logical_operators;
92 self.exception_handlers += nested.exception_handlers;
93 self.early_returns += nested.early_returns;
94 }
96
97 pub fn with_branches(mut self, count: u32) -> Self {
100 self.branches = count;
101 self
102 }
103
104 pub fn with_loops(mut self, count: u32) -> Self {
105 self.loops = count;
106 self
107 }
108
109 pub fn with_logical_operators(mut self, count: u32) -> Self {
110 self.logical_operators = count;
111 self
112 }
113
114 pub fn with_nesting_depth(mut self, depth: u32) -> Self {
115 self.max_nesting_depth = depth;
116 self
117 }
118
119 pub fn with_exception_handlers(mut self, count: u32) -> Self {
120 self.exception_handlers = count;
121 self
122 }
123
124 pub fn with_early_returns(mut self, count: u32) -> Self {
125 self.early_returns = count;
126 self
127 }
128
129 pub fn finalize(mut self) -> Self {
131 self.calculate_cyclomatic();
132 self
133 }
134}
135
136#[derive(Debug, Default)]
138pub struct ComplexityBuilder {
139 metrics: ComplexityMetrics,
140 current_nesting: u32,
141}
142
143impl ComplexityBuilder {
144 pub fn new() -> Self {
145 Self {
146 metrics: ComplexityMetrics::new(),
147 current_nesting: 0,
148 }
149 }
150
151 pub fn add_branch(&mut self) {
153 self.metrics.branches += 1;
154 }
155
156 pub fn add_loop(&mut self) {
158 self.metrics.loops += 1;
159 }
160
161 pub fn add_logical_operator(&mut self) {
163 self.metrics.logical_operators += 1;
164 }
165
166 pub fn add_exception_handler(&mut self) {
168 self.metrics.exception_handlers += 1;
169 }
170
171 pub fn add_early_return(&mut self) {
173 self.metrics.early_returns += 1;
174 }
175
176 pub fn enter_scope(&mut self) {
178 self.current_nesting += 1;
179 if self.current_nesting > self.metrics.max_nesting_depth {
180 self.metrics.max_nesting_depth = self.current_nesting;
181 }
182 }
183
184 pub fn exit_scope(&mut self) {
186 self.current_nesting = self.current_nesting.saturating_sub(1);
187 }
188
189 pub fn current_depth(&self) -> u32 {
191 self.current_nesting
192 }
193
194 pub fn build(mut self) -> ComplexityMetrics {
196 self.metrics.calculate_cyclomatic();
197 self.metrics
198 }
199
200 pub fn current(&self) -> &ComplexityMetrics {
202 &self.metrics
203 }
204}
205
206#[cfg(test)]
207mod tests {
208 use super::*;
209
210 #[test]
211 fn test_new_has_base_complexity() {
212 let metrics = ComplexityMetrics::new();
213 assert_eq!(metrics.cyclomatic_complexity, 1);
214 }
215
216 #[test]
217 fn test_grade_simple() {
218 let metrics = ComplexityMetrics {
219 cyclomatic_complexity: 3,
220 ..Default::default()
221 };
222 assert_eq!(metrics.grade(), 'A');
223 }
224
225 #[test]
226 fn test_grade_moderate() {
227 let metrics = ComplexityMetrics {
228 cyclomatic_complexity: 8,
229 ..Default::default()
230 };
231 assert_eq!(metrics.grade(), 'B');
232 }
233
234 #[test]
235 fn test_grade_complex() {
236 let metrics = ComplexityMetrics {
237 cyclomatic_complexity: 15,
238 ..Default::default()
239 };
240 assert_eq!(metrics.grade(), 'C');
241 }
242
243 #[test]
244 fn test_grade_very_complex() {
245 let metrics = ComplexityMetrics {
246 cyclomatic_complexity: 35,
247 ..Default::default()
248 };
249 assert_eq!(metrics.grade(), 'D');
250 }
251
252 #[test]
253 fn test_grade_untestable() {
254 let metrics = ComplexityMetrics {
255 cyclomatic_complexity: 60,
256 ..Default::default()
257 };
258 assert_eq!(metrics.grade(), 'F');
259 }
260
261 #[test]
262 fn test_calculate_cyclomatic() {
263 let mut metrics = ComplexityMetrics::new()
264 .with_branches(3)
265 .with_loops(2)
266 .with_logical_operators(1);
267 metrics.calculate_cyclomatic();
268 assert_eq!(metrics.cyclomatic_complexity, 7);
270 }
271
272 #[test]
273 fn test_builder_basic() {
274 let mut builder = ComplexityBuilder::new();
275 builder.add_branch();
276 builder.add_branch();
277 builder.add_loop();
278
279 let metrics = builder.build();
280 assert_eq!(metrics.cyclomatic_complexity, 4);
282 }
283
284 #[test]
285 fn test_builder_nesting() {
286 let mut builder = ComplexityBuilder::new();
287 builder.enter_scope();
288 builder.add_branch();
289 builder.enter_scope();
290 builder.add_loop();
291 builder.enter_scope();
292 builder.exit_scope();
293 builder.exit_scope();
294 builder.exit_scope();
295
296 let metrics = builder.build();
297 assert_eq!(metrics.max_nesting_depth, 3);
298 }
299
300 #[test]
301 fn test_exceeds_threshold() {
302 let metrics = ComplexityMetrics {
303 cyclomatic_complexity: 15,
304 ..Default::default()
305 };
306 assert!(metrics.exceeds_threshold(10));
307 assert!(!metrics.exceeds_threshold(20));
308 }
309
310 #[test]
311 fn test_has_high_nesting() {
312 let low_nesting = ComplexityMetrics {
313 max_nesting_depth: 3,
314 ..Default::default()
315 };
316 assert!(!low_nesting.has_high_nesting());
317
318 let high_nesting = ComplexityMetrics {
319 max_nesting_depth: 5,
320 ..Default::default()
321 };
322 assert!(high_nesting.has_high_nesting());
323 }
324}