geographdb_core/algorithms/
complexity.rs1#[derive(Debug, Clone)]
39pub struct ComplexityBlock {
40 pub id: u64,
41 pub z: f32, }
43
44#[derive(Debug, Clone)]
46pub struct ComplexityResult {
47 pub cyclomatic_complexity: u32,
49 pub block_count: usize,
51 pub total_branches: u32,
53 pub avg_branches_per_block: f32,
55 pub rating: ComplexityRating,
57}
58
59#[derive(Debug, Clone, Copy, PartialEq, Eq)]
61pub enum ComplexityRating {
62 Low,
64 Medium,
66 High,
68 VeryHigh,
70}
71
72impl ComplexityRating {
73 pub fn as_str(&self) -> &'static str {
74 match self {
75 ComplexityRating::Low => "Low (1-10)",
76 ComplexityRating::Medium => "Medium (11-20)",
77 ComplexityRating::High => "High (21-50)",
78 ComplexityRating::VeryHigh => "Very High (51+)",
79 }
80 }
81
82 pub fn from_complexity(complexity: u32) -> Self {
83 match complexity {
84 0..=10 => ComplexityRating::Low,
85 11..=20 => ComplexityRating::Medium,
86 21..=50 => ComplexityRating::High,
87 _ => ComplexityRating::VeryHigh,
88 }
89 }
90}
91
92pub fn calculate_cyclomatic_complexity(blocks: &[ComplexityBlock]) -> ComplexityResult {
98 let block_count = blocks.len();
99 let total_branches: u32 = blocks.iter().map(|b| b.z as u32).sum();
100
101 let cyclomatic_complexity = 1 + total_branches;
103
104 let avg_branches = if block_count > 0 {
105 total_branches as f32 / block_count as f32
106 } else {
107 0.0
108 };
109
110 let rating = ComplexityRating::from_complexity(cyclomatic_complexity);
111
112 ComplexityResult {
113 cyclomatic_complexity,
114 block_count,
115 total_branches,
116 avg_branches_per_block: avg_branches,
117 rating,
118 }
119}
120
121pub fn calculate_per_block_complexity(blocks: &[ComplexityBlock]) -> Vec<BlockComplexity> {
123 blocks
124 .iter()
125 .map(|b| {
126 BlockComplexity {
127 block_id: b.id,
128 branch_count: b.z as u32,
129 local_complexity: 1 + b.z as u32,
131 }
132 })
133 .collect()
134}
135
136#[derive(Debug, Clone)]
138pub struct BlockComplexity {
139 pub block_id: u64,
140 pub branch_count: u32,
141 pub local_complexity: u32,
142}
143
144pub fn find_most_complex_blocks(blocks: &[ComplexityBlock], n: usize) -> Vec<BlockComplexity> {
146 let mut complexities: Vec<BlockComplexity> = calculate_per_block_complexity(blocks);
147
148 complexities.sort_by_key(|block| std::cmp::Reverse(block.local_complexity));
150
151 complexities.into_iter().take(n).collect()
153}
154
155pub fn is_complexity_acceptable(complexity: u32, threshold: u32) -> bool {
157 complexity <= threshold
158}
159
160pub fn get_complexity_trend(complexity: u32) -> &'static str {
162 match complexity {
163 0..=5 => "Very simple - easy to understand and test",
164 6..=10 => "Simple - well-structured code",
165 11..=15 => "Moderate - consider adding comments",
166 16..=20 => "Complex - consider refactoring",
167 21..=30 => "Very complex - should be refactored",
168 _ => "Extremely complex - immediate refactoring recommended",
169 }
170}
171
172#[cfg(test)]
173mod tests {
174 use super::*;
175
176 #[test]
177 fn test_cyclomatic_complexity_simple() {
178 let blocks = vec![
180 ComplexityBlock { id: 0, z: 0.0 },
181 ComplexityBlock { id: 1, z: 0.0 },
182 ComplexityBlock { id: 2, z: 0.0 },
183 ];
184
185 let result = calculate_cyclomatic_complexity(&blocks);
186
187 assert_eq!(
188 result.cyclomatic_complexity, 1,
189 "Simple function should have complexity 1"
190 );
191 assert_eq!(result.block_count, 3);
192 assert_eq!(result.total_branches, 0);
193 assert_eq!(result.rating, ComplexityRating::Low);
194 }
195
196 #[test]
197 fn test_cyclomatic_complexity_single_if() {
198 let blocks = vec![
200 ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 0.0 }, ComplexityBlock { id: 3, z: 0.0 }, ];
205
206 let result = calculate_cyclomatic_complexity(&blocks);
207
208 assert_eq!(
209 result.cyclomatic_complexity, 2,
210 "Single if should have complexity 2"
211 );
212 assert_eq!(result.total_branches, 1);
213 }
214
215 #[test]
216 fn test_cyclomatic_complexity_nested_if() {
217 let blocks = vec![
219 ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 1.0 }, ComplexityBlock { id: 3, z: 0.0 }, ComplexityBlock { id: 4, z: 0.0 }, ];
225
226 let result = calculate_cyclomatic_complexity(&blocks);
227
228 assert_eq!(
229 result.cyclomatic_complexity, 3,
230 "Nested if should have complexity 3"
231 );
232 assert_eq!(result.total_branches, 2);
233 }
234
235 #[test]
236 fn test_cyclomatic_complexity_multiple_branches() {
237 let blocks = vec![
239 ComplexityBlock { id: 0, z: 0.0 }, ComplexityBlock { id: 1, z: 1.0 }, ComplexityBlock { id: 2, z: 1.0 }, ComplexityBlock { id: 3, z: 1.0 }, ComplexityBlock { id: 4, z: 0.0 }, ];
245
246 let result = calculate_cyclomatic_complexity(&blocks);
247
248 assert_eq!(
249 result.cyclomatic_complexity, 4,
250 "Three ifs should have complexity 4"
251 );
252 }
253
254 #[test]
255 fn test_complexity_rating_low() {
256 let blocks: Vec<ComplexityBlock> =
257 (0..10).map(|i| ComplexityBlock { id: i, z: 0.0 }).collect();
258 let result = calculate_cyclomatic_complexity(&blocks);
259 assert_eq!(result.rating, ComplexityRating::Low);
260 }
261
262 #[test]
263 fn test_complexity_rating_medium() {
264 let blocks: Vec<ComplexityBlock> =
266 (0..15).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
267 let result = calculate_cyclomatic_complexity(&blocks);
268 assert_eq!(result.rating, ComplexityRating::Medium);
269 }
270
271 #[test]
272 fn test_complexity_rating_high() {
273 let blocks: Vec<ComplexityBlock> =
275 (0..30).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
276 let result = calculate_cyclomatic_complexity(&blocks);
277 assert_eq!(result.rating, ComplexityRating::High);
278 }
279
280 #[test]
281 fn test_complexity_rating_very_high() {
282 let blocks: Vec<ComplexityBlock> =
284 (0..60).map(|i| ComplexityBlock { id: i, z: 1.0 }).collect();
285 let result = calculate_cyclomatic_complexity(&blocks);
286 assert_eq!(result.rating, ComplexityRating::VeryHigh);
287 }
288
289 #[test]
290 fn test_per_block_complexity() {
291 let blocks = vec![
292 ComplexityBlock { id: 0, z: 0.0 },
293 ComplexityBlock { id: 1, z: 2.0 }, ComplexityBlock { id: 2, z: 0.0 },
295 ];
296
297 let complexities = calculate_per_block_complexity(&blocks);
298
299 assert_eq!(complexities.len(), 3);
300 assert_eq!(complexities[1].local_complexity, 3); }
302
303 #[test]
304 fn test_find_most_complex_blocks() {
305 let blocks = vec![
306 ComplexityBlock { id: 0, z: 0.0 },
307 ComplexityBlock { id: 1, z: 3.0 }, ComplexityBlock { id: 2, z: 1.0 },
309 ComplexityBlock { id: 3, z: 2.0 }, ComplexityBlock { id: 4, z: 0.0 },
311 ];
312
313 let top = find_most_complex_blocks(&blocks, 2);
314
315 assert_eq!(top.len(), 2);
316 assert_eq!(top[0].block_id, 1); assert_eq!(top[1].block_id, 3); }
319
320 #[test]
321 fn test_is_complexity_acceptable() {
322 assert!(
323 is_complexity_acceptable(5, 10),
324 "5 should be acceptable with threshold 10"
325 );
326 assert!(
327 !is_complexity_acceptable(15, 10),
328 "15 should not be acceptable with threshold 10"
329 );
330 assert!(
331 is_complexity_acceptable(10, 10),
332 "10 should be acceptable with threshold 10"
333 );
334 }
335
336 #[test]
337 fn test_get_complexity_trend() {
338 assert!(get_complexity_trend(3).contains("simple"));
339 assert!(get_complexity_trend(25).contains("complex"));
340 assert!(get_complexity_trend(60).contains("refactoring"));
341 }
342
343 #[test]
344 fn test_avg_branches_per_block() {
345 let blocks = vec![
346 ComplexityBlock { id: 0, z: 0.0 },
347 ComplexityBlock { id: 1, z: 2.0 },
348 ComplexityBlock { id: 2, z: 4.0 },
349 ];
350
351 let result = calculate_cyclomatic_complexity(&blocks);
352
353 assert!((result.avg_branches_per_block - 2.0).abs() < 0.001);
355 }
356}