rust_code_analysis/metrics/
cyclomatic.rs1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::*;
7
8#[derive(Debug, Clone)]
10pub struct Stats {
11 cyclomatic_sum: f64,
12 cyclomatic: f64,
13 n: usize,
14 cyclomatic_max: f64,
15 cyclomatic_min: f64,
16}
17
18impl Default for Stats {
19 fn default() -> Self {
20 Self {
21 cyclomatic_sum: 0.,
22 cyclomatic: 1.,
23 n: 1,
24 cyclomatic_max: 0.,
25 cyclomatic_min: f64::MAX,
26 }
27 }
28}
29
30impl Serialize for Stats {
31 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
32 where
33 S: Serializer,
34 {
35 let mut st = serializer.serialize_struct("cyclomatic", 4)?;
36 st.serialize_field("sum", &self.cyclomatic_sum())?;
37 st.serialize_field("average", &self.cyclomatic_average())?;
38 st.serialize_field("min", &self.cyclomatic_min())?;
39 st.serialize_field("max", &self.cyclomatic_max())?;
40 st.end()
41 }
42}
43
44impl fmt::Display for Stats {
45 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
46 write!(
47 f,
48 "sum: {}, average: {}, min: {}, max: {}",
49 self.cyclomatic_sum(),
50 self.cyclomatic_average(),
51 self.cyclomatic_min(),
52 self.cyclomatic_max()
53 )
54 }
55}
56
57impl Stats {
58 pub fn merge(&mut self, other: &Stats) {
60 self.cyclomatic_max = self.cyclomatic_max.max(other.cyclomatic_max);
62 self.cyclomatic_min = self.cyclomatic_min.min(other.cyclomatic_min);
63
64 self.cyclomatic_sum += other.cyclomatic_sum;
65 self.n += other.n;
66 }
67
68 pub fn cyclomatic(&self) -> f64 {
70 self.cyclomatic
71 }
72 pub fn cyclomatic_sum(&self) -> f64 {
74 self.cyclomatic_sum
75 }
76
77 pub fn cyclomatic_average(&self) -> f64 {
82 self.cyclomatic_sum() / self.n as f64
83 }
84 pub fn cyclomatic_max(&self) -> f64 {
86 self.cyclomatic_max
87 }
88 pub fn cyclomatic_min(&self) -> f64 {
90 self.cyclomatic_min
91 }
92 #[inline(always)]
93 pub(crate) fn compute_sum(&mut self) {
94 self.cyclomatic_sum += self.cyclomatic;
95 }
96 #[inline(always)]
97 pub(crate) fn compute_minmax(&mut self) {
98 self.cyclomatic_max = self.cyclomatic_max.max(self.cyclomatic);
99 self.cyclomatic_min = self.cyclomatic_min.min(self.cyclomatic);
100 self.compute_sum();
101 }
102}
103
104#[doc(hidden)]
105pub trait Cyclomatic
106where
107 Self: Checker,
108{
109 fn compute(_node: &Node, _stats: &mut Stats) {}
110}
111
112impl Cyclomatic for PythonCode {
113 fn compute(node: &Node, stats: &mut Stats) {
114 use Python::*;
115
116 match node.object().kind_id().into() {
117 If | Elif | For | While | Except | With | Assert | And | Or => {
118 stats.cyclomatic += 1.;
119 }
120 Else => {
121 if has_ancestors!(node, ForStatement | WhileStatement, ElseClause) {
122 stats.cyclomatic += 1.;
123 }
124 }
125 _ => {}
126 }
127 }
128}
129
130impl Cyclomatic for MozjsCode {
131 fn compute(node: &Node, stats: &mut Stats) {
132 use Mozjs::*;
133
134 match node.object().kind_id().into() {
135 If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
136 stats.cyclomatic += 1.;
137 }
138 _ => {}
139 }
140 }
141}
142
143impl Cyclomatic for JavascriptCode {
144 fn compute(node: &Node, stats: &mut Stats) {
145 use Javascript::*;
146
147 match node.object().kind_id().into() {
148 If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
149 stats.cyclomatic += 1.;
150 }
151 _ => {}
152 }
153 }
154}
155
156impl Cyclomatic for TypescriptCode {
157 fn compute(node: &Node, stats: &mut Stats) {
158 use Typescript::*;
159
160 match node.object().kind_id().into() {
161 If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
162 stats.cyclomatic += 1.;
163 }
164 _ => {}
165 }
166 }
167}
168
169impl Cyclomatic for TsxCode {
170 fn compute(node: &Node, stats: &mut Stats) {
171 use Tsx::*;
172
173 match node.object().kind_id().into() {
174 If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
175 stats.cyclomatic += 1.;
176 }
177 _ => {}
178 }
179 }
180}
181
182impl Cyclomatic for RustCode {
183 fn compute(node: &Node, stats: &mut Stats) {
184 use Rust::*;
185
186 match node.object().kind_id().into() {
187 If | For | While | Loop | MatchArm | MatchArm2 | QMARK | AMPAMP | PIPEPIPE => {
188 stats.cyclomatic += 1.;
189 }
190 _ => {}
191 }
192 }
193}
194
195impl Cyclomatic for CppCode {
196 fn compute(node: &Node, stats: &mut Stats) {
197 use Cpp::*;
198
199 match node.object().kind_id().into() {
200 If | For | While | Case | Catch | ConditionalExpression | AMPAMP | PIPEPIPE => {
201 stats.cyclomatic += 1.;
202 }
203 _ => {}
204 }
205 }
206}
207
208impl Cyclomatic for PreprocCode {}
209impl Cyclomatic for CcommentCode {}
210
211impl Cyclomatic for JavaCode {
212 fn compute(node: &Node, stats: &mut Stats) {
213 use Java::*;
214
215 match node.object().kind_id().into() {
216 If | For | While | Case | Catch | TernaryExpression | AMPAMP | PIPEPIPE => {
217 stats.cyclomatic += 1.;
218 }
219 _ => {}
220 }
221 }
222}
223
224#[cfg(test)]
225mod tests {
226 use std::path::PathBuf;
227
228 use super::*;
229
230 #[test]
231 fn python_simple_function() {
232 check_metrics!(
233 "def f(a, b): # +2 (+1 unit space)
234 if a and b: # +2 (+1 and)
235 return 1
236 if c and d: # +2 (+1 and)
237 return 1",
238 "foo.py",
239 PythonParser,
240 cyclomatic,
241 [(cyclomatic_sum, 6, usize)],
242 [
243 (cyclomatic_average, 3.0), (cyclomatic_max, 5.0),
245 (cyclomatic_min, 1.0)
246 ]
247 );
248 }
249
250 #[test]
251 fn python_1_level_nesting() {
252 check_metrics!(
253 "def f(a, b): # +2 (+1 unit space)
254 if a: # +1
255 for i in range(b): # +1
256 return 1",
257 "foo.py",
258 PythonParser,
259 cyclomatic,
260 [(cyclomatic_sum, 4, usize)],
261 [
262 (cyclomatic_average, 2.0), (cyclomatic_max, 3.0),
264 (cyclomatic_min, 1.0)
265 ]
266 );
267 }
268
269 #[test]
270 fn rust_1_level_nesting() {
271 check_metrics!(
272 "fn f() { // +2 (+1 unit space)
273 if true { // +1
274 match true {
275 true => println!(\"test\"), // +1
276 false => println!(\"test\"), // +1
277 }
278 }
279 }",
280 "foo.rs",
281 RustParser,
282 cyclomatic,
283 [(cyclomatic_sum, 5, usize)],
284 [
285 (cyclomatic_average, 2.5), (cyclomatic_max, 4.0),
287 (cyclomatic_min, 1.0)
288 ]
289 );
290 }
291
292 #[test]
293 fn c_switch() {
294 check_metrics!(
295 "void f() { // +2 (+1 unit space)
296 switch (1) {
297 case 1: // +1
298 printf(\"one\");
299 break;
300 case 2: // +1
301 printf(\"two\");
302 break;
303 case 3: // +1
304 printf(\"three\");
305 break;
306 default:
307 printf(\"all\");
308 break;
309 }
310 }",
311 "foo.c",
312 CppParser,
313 cyclomatic,
314 [(cyclomatic_sum, 5, usize)],
315 [
316 (cyclomatic_average, 2.5), (cyclomatic_max, 4.0),
318 (cyclomatic_min, 1.0)
319 ]
320 );
321 }
322
323 #[test]
324 fn c_real_function() {
325 check_metrics!(
326 "int sumOfPrimes(int max) { // +2 (+1 unit space)
327 int total = 0;
328 OUT: for (int i = 1; i <= max; ++i) { // +1
329 for (int j = 2; j < i; ++j) { // +1
330 if (i % j == 0) { // +1
331 continue OUT;
332 }
333 }
334 total += i;
335 }
336 return total;
337 }",
338 "foo.c",
339 CppParser,
340 cyclomatic,
341 [(cyclomatic_sum, 5, usize)],
342 [
343 (cyclomatic_average, 2.5), (cyclomatic_max, 4.0),
345 (cyclomatic_min, 1.0)
346 ]
347 );
348 }
349 #[test]
350 fn c_unit_before() {
351 check_metrics!(
352 "
353 int a=42;
354 if(a==42) //+2(+1 unit space)
355 {
356
357 }
358 if(a==34) //+1
359 {
360
361 }
362 int sumOfPrimes(int max) { // +1
363 int total = 0;
364 OUT: for (int i = 1; i <= max; ++i) { // +1
365 for (int j = 2; j < i; ++j) { // +1
366 if (i % j == 0) { // +1
367 continue OUT;
368 }
369 }
370 total += i;
371 }
372 return total;
373 }",
374 "foo.c",
375 CppParser,
376 cyclomatic,
377 [(cyclomatic_sum, 7, usize)],
378 [
379 (cyclomatic_average, 3.5), (cyclomatic_max, 4.0),
381 (cyclomatic_min, 3.0)
382 ]
383 );
384 }
385 #[test]
389 fn c_unit_after() {
390 check_metrics!(
391 "
392 int sumOfPrimes(int max) { // +1
393 int total = 0;
394 OUT: for (int i = 1; i <= max; ++i) { // +1
395 for (int j = 2; j < i; ++j) { // +1
396 if (i % j == 0) { // +1
397 continue OUT;
398 }
399 }
400 total += i;
401 }
402 return total;
403 }
404
405 int a=42;
406 if(a==42) //+2(+1 unit space)
407 {
408
409 }
410 if(a==34) //+1
411 {
412
413 }",
414 "foo.c",
415 CppParser,
416 cyclomatic,
417 [(cyclomatic_sum, 7, usize)],
418 [
419 (cyclomatic_average, 3.5), (cyclomatic_max, 4.0),
421 (cyclomatic_min, 3.0)
422 ]
423 );
424 }
425
426 #[test]
427 fn java_simple_class() {
428 check_metrics!(
429 "
430 public class Example { // +2 (+1 unit space)
431 int a = 10;
432 boolean b = (a > 5) ? true : false; // +1
433 boolean c = b && true; // +1
434
435 public void m1() { // +1
436 if (a % 2 == 0) { // +1
437 b = b || c; // +1
438 }
439 }
440 public void m2() { // +1
441 while (a > 3) { // +1
442 m1();
443 a--;
444 }
445 }
446 }",
447 "foo.java",
448 JavaParser,
449 cyclomatic,
450 [(cyclomatic_sum, 9, usize)],
451 [
452 (cyclomatic_average, 2.25), (cyclomatic_max, 3.0),
454 (cyclomatic_min, 1.0)
455 ]
456 );
457 }
458
459 #[test]
460 fn java_real_class() {
461 check_metrics!(
462 "
463 public class Matrix { // +2 (+1 unit space)
464 private int[][] m = new int[5][5];
465
466 public void init() { // +1
467 for (int i = 0; i < m.length; i++) { // +1
468 for (int j = 0; j < m[i].length; j++) { // +1
469 m[i][j] = i * j;
470 }
471 }
472 }
473 public int compute(int i, int j) { // +1
474 try {
475 return m[i][j] / m[j][i];
476 } catch (ArithmeticException e) { // +1
477 return -1;
478 } catch (ArrayIndexOutOfBoundsException e) { // +1
479 return -2;
480 }
481 }
482 public void print(int result) { // +1
483 switch (result) {
484 case -1: // +1
485 System.out.println(\"Division by zero\");
486 break;
487 case -2: // +1
488 System.out.println(\"Wrong index number\");
489 break;
490 default:
491 System.out.println(\"The result is \" + result);
492 }
493 }
494 }",
495 "foo.java",
496 JavaParser,
497 cyclomatic,
498 [(cyclomatic_sum, 11, usize)],
499 [
500 (cyclomatic_average, 2.2), (cyclomatic_max, 3.0),
502 (cyclomatic_min, 1.0)
503 ]
504 );
505 }
506
507 #[test]
512 fn java_anonymous_class() {
513 check_metrics!(
514 "
515 abstract class A { // +2 (+1 unit space)
516 public abstract boolean m1(int n); // +1
517 public abstract boolean m2(int n); // +1
518 }
519 public class B { // +1
520
521 public void test() { // +1
522 A a = new A() {
523 public boolean m1(int n) { // +1
524 if (n % 2 == 0) { // +1
525 return true;
526 }
527 return false;
528 }
529 public boolean m2(int n) { // +1
530 if (n % 5 == 0) { // +1
531 return true;
532 }
533 return false;
534 }
535 };
536 }
537 }",
538 "foo.java",
539 JavaParser,
540 cyclomatic,
541 [(cyclomatic_sum, 10, usize)],
542 [
543 (cyclomatic_average, 1.25), (cyclomatic_max, 2.0),
545 (cyclomatic_min, 1.0)
546 ]
547 );
548 }
549}