1use serde::Serialize;
2use serde::ser::{SerializeStruct, Serializer};
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::langs::*;
7use crate::macros::implement_metric_trait;
8use crate::node::Node;
9use crate::*;
10
11#[derive(Clone, Debug, Default)]
16pub struct Stats {
17 class_npa: usize,
18 interface_npa: usize,
19 class_na: usize,
20 interface_na: usize,
21 class_npa_sum: usize,
22 interface_npa_sum: usize,
23 class_na_sum: usize,
24 interface_na_sum: usize,
25 is_class_space: bool,
26}
27
28impl Serialize for Stats {
29 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
30 where
31 S: Serializer,
32 {
33 let mut st = serializer.serialize_struct("npa", 9)?;
34 st.serialize_field("classes", &self.class_npa_sum())?;
35 st.serialize_field("interfaces", &self.interface_npa_sum())?;
36 st.serialize_field("class_attributes", &self.class_na_sum())?;
37 st.serialize_field("interface_attributes", &self.interface_na_sum())?;
38 st.serialize_field("classes_average", &self.class_cda())?;
39 st.serialize_field("interfaces_average", &self.interface_cda())?;
40 st.serialize_field("total", &self.total_npa())?;
41 st.serialize_field("total_attributes", &self.total_na())?;
42 st.serialize_field("average", &self.total_cda())?;
43 st.end()
44 }
45}
46
47impl fmt::Display for Stats {
48 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
49 write!(
50 f,
51 "classes: {}, interfaces: {}, class_attributes: {}, interface_attributes: {}, classes_average: {}, interfaces_average: {}, total: {}, total_attributes: {}, average: {}",
52 self.class_npa_sum(),
53 self.interface_npa_sum(),
54 self.class_na_sum(),
55 self.interface_na_sum(),
56 self.class_cda(),
57 self.interface_cda(),
58 self.total_npa(),
59 self.total_na(),
60 self.total_cda()
61 )
62 }
63}
64
65impl Stats {
66 pub fn merge(&mut self, other: &Stats) {
68 self.class_npa_sum += other.class_npa_sum;
69 self.interface_npa_sum += other.interface_npa_sum;
70 self.class_na_sum += other.class_na_sum;
71 self.interface_na_sum += other.interface_na_sum;
72 }
73
74 #[inline(always)]
76 pub fn class_npa(&self) -> f64 {
77 self.class_npa as f64
78 }
79
80 #[inline(always)]
82 pub fn interface_npa(&self) -> f64 {
83 self.interface_npa as f64
84 }
85
86 #[inline(always)]
88 pub fn class_na(&self) -> f64 {
89 self.class_na as f64
90 }
91
92 #[inline(always)]
94 pub fn interface_na(&self) -> f64 {
95 self.interface_na as f64
96 }
97
98 #[inline(always)]
100 pub fn class_npa_sum(&self) -> f64 {
101 self.class_npa_sum as f64
102 }
103
104 #[inline(always)]
106 pub fn interface_npa_sum(&self) -> f64 {
107 self.interface_npa_sum as f64
108 }
109
110 #[inline(always)]
112 pub fn class_na_sum(&self) -> f64 {
113 self.class_na_sum as f64
114 }
115
116 #[inline(always)]
118 pub fn interface_na_sum(&self) -> f64 {
119 self.interface_na_sum as f64
120 }
121
122 #[inline(always)]
132 pub fn class_cda(&self) -> f64 {
133 self.class_npa_sum() / self.class_na_sum as f64
134 }
135
136 #[inline(always)]
146 pub fn interface_cda(&self) -> f64 {
147 if self.interface_npa_sum == self.interface_na_sum && self.interface_npa_sum != 0 {
150 1.0
151 } else {
152 self.interface_npa_sum() / self.interface_na_sum()
153 }
154 }
155
156 #[inline(always)]
166 pub fn total_cda(&self) -> f64 {
167 self.total_npa() / self.total_na()
168 }
169
170 #[inline(always)]
172 pub fn total_npa(&self) -> f64 {
173 self.class_npa_sum() + self.interface_npa_sum()
174 }
175
176 #[inline(always)]
178 pub fn total_na(&self) -> f64 {
179 self.class_na_sum() + self.interface_na_sum()
180 }
181
182 #[inline(always)]
185 pub(crate) fn compute_sum(&mut self) {
186 self.class_npa_sum += self.class_npa;
187 self.interface_npa_sum += self.interface_npa;
188 self.class_na_sum += self.class_na;
189 self.interface_na_sum += self.interface_na;
190 }
191
192 #[inline(always)]
194 pub(crate) fn is_disabled(&self) -> bool {
195 !self.is_class_space
196 }
197}
198
199pub trait Npa
200where
201 Self: Checker,
202{
203 fn compute(node: &Node, stats: &mut Stats);
204}
205
206impl Npa for JavaCode {
207 fn compute(node: &Node, stats: &mut Stats) {
208 use Java::*;
209
210 if Self::is_func_space(node) && stats.is_disabled() {
212 stats.is_class_space = true;
213 }
214
215 match node.kind_id().into() {
216 ClassBody => {
217 stats.class_na += node
218 .children()
219 .filter(|node| matches!(node.kind_id().into(), FieldDeclaration))
220 .map(|declaration| {
221 let attributes = declaration
222 .children()
223 .filter(|n| matches!(n.kind_id().into(), VariableDeclarator))
224 .count();
225 if declaration.child(0).is_some_and(|modifiers| {
229 matches!(modifiers.kind_id().into(), Modifiers)
231 && modifiers.first_child(|id| id == Public).is_some()
232 }) {
233 stats.class_npa += attributes;
234 }
235 attributes
236 })
237 .sum::<usize>();
238 }
239 InterfaceBody => {
242 stats.interface_na += node
246 .children()
247 .filter(|node| matches!(node.kind_id().into(), ConstantDeclaration))
248 .flat_map(|node| node.children())
249 .filter(|node| matches!(node.kind_id().into(), VariableDeclarator))
250 .count();
251 stats.interface_npa = stats.interface_na;
252 }
253 _ => {}
254 }
255 }
256}
257
258implement_metric_trait!(
259 Npa,
260 PythonCode,
261 MozjsCode,
262 JavascriptCode,
263 TypescriptCode,
264 TsxCode,
265 RustCode,
266 CppCode,
267 PreprocCode,
268 CcommentCode,
269 KotlinCode
270);
271
272#[cfg(test)]
273mod tests {
274 use crate::tools::check_metrics;
275
276 use super::*;
277
278 #[test]
279 fn java_single_attributes() {
280 check_metrics::<JavaParser>(
281 "class X {
282 public byte a; // +1
283 public short b; // +1
284 public int c; // +1
285 public long d; // +1
286 public float e; // +1
287 public double f; // +1
288 public boolean g; // +1
289 public char h; // +1
290 byte i;
291 short j;
292 int k;
293 long l;
294 float m;
295 double n;
296 boolean o;
297 char p;
298 }",
299 "foo.java",
300 |metric| {
301 insta::assert_json_snapshot!(
302 metric.npa,
303 @r###"
304 {
305 "classes": 8.0,
306 "interfaces": 0.0,
307 "class_attributes": 16.0,
308 "interface_attributes": 0.0,
309 "classes_average": 0.5,
310 "interfaces_average": null,
311 "total": 8.0,
312 "total_attributes": 16.0,
313 "average": 0.5
314 }"###
315 );
316 },
317 );
318 }
319
320 #[test]
321 fn java_multiple_attributes() {
322 check_metrics::<JavaParser>(
323 "class X {
324 public byte a1; // +1
325 public short b1, b2; // +2
326 public int c1, c2, c3; // +3
327 public long d1, d2, d3, d4; // +4
328 public float e1, e2, e3, e4; // +4
329 public double f1, f2, f3; // +3
330 public boolean g1, g2; // +2
331 public char h1; // +1
332 byte i1, i2, i3, i4;
333 short j1, j2, j3;
334 int k1, k2;
335 long l1;
336 float m1;
337 double n1, n2;
338 boolean o1, o2, o3;
339 char p1, p2, p3, p4;
340 }",
341 "foo.java",
342 |metric| {
343 insta::assert_json_snapshot!(
344 metric.npa,
345 @r###"
346 {
347 "classes": 20.0,
348 "interfaces": 0.0,
349 "class_attributes": 40.0,
350 "interface_attributes": 0.0,
351 "classes_average": 0.5,
352 "interfaces_average": null,
353 "total": 20.0,
354 "total_attributes": 40.0,
355 "average": 0.5
356 }"###
357 );
358 },
359 );
360 }
361
362 #[test]
363 fn java_initialized_attributes() {
364 check_metrics::<JavaParser>(
365 "class X {
366 public byte a1 = 1; // +1
367 public short b1 = 2, b2; // +2
368 public int c1, c2 = 3, c3; // +3
369 public long d1 = 4, d2, d3, d4 = 5; // +4
370 public float e1, e2 = 6.0f, e3 = 7.0f, e4; // +4
371 public double f1 = 8.0, f2 = 9.0, f3 = 10.0; // +3
372 public boolean g1 = true, g2; // +2
373 public char h1 = 'a'; // +1
374 byte i1 = 1, i2 = 2, i3 = 3, i4 = 4;
375 short j1 = 5, j2, j3 = 6;
376 int k1, k2 = 7;
377 long l1 = 8;
378 float m1 = 9.0f;
379 double n1, n2 = 10.0;
380 boolean o1, o2 = false, o3;
381 char p1 = 'a', p2 = 'b', p3 = 'c', p4 = 'd';
382 }",
383 "foo.java",
384 |metric| {
385 insta::assert_json_snapshot!(
386 metric.npa,
387 @r###"
388 {
389 "classes": 20.0,
390 "interfaces": 0.0,
391 "class_attributes": 40.0,
392 "interface_attributes": 0.0,
393 "classes_average": 0.5,
394 "interfaces_average": null,
395 "total": 20.0,
396 "total_attributes": 40.0,
397 "average": 0.5
398 }"###
399 );
400 },
401 );
402 }
403
404 #[test]
405 fn java_array_attributes() {
406 check_metrics::<JavaParser>(
407 "class X {
408 public byte[] a1, a2, a3, a4; // +4
409 public short b1[], b2[], b3[]; // +3
410 public int[] c1 = { 1 }, c2; // +2
411 public long d1[] = { 1 }; // +1
412 public float[] e1 = { 1.0f, 2.0f, 3.0f }; // +1
413 public double f1[] = { 1.0, 2.0, 3.0 }, f2[]; // +2
414 public boolean[] g1 = new boolean[5], g2, g3; // +3
415 public char[] h1 = new char[5], h2[], h3[], h4[]; // +4
416 byte[] i1;
417 short j1[], j2[];
418 int[] k1, k2, k3 = { 1 };
419 long l1[], l2[] = { 1 }, l3[] = { 2 }, l4[];
420 float[] m1, m2, m3, m4 = { 1.0f, 2.0f, 3.0f };
421 double n1[], n2[] = { 1.0, 2.0, 3.0 }, n3[];
422 boolean[] o1, o2 = new boolean[5];
423 char[] p1 = new char[5];
424 }",
425 "foo.java",
426 |metric| {
427 insta::assert_json_snapshot!(
428 metric.npa,
429 @r###"
430 {
431 "classes": 20.0,
432 "interfaces": 0.0,
433 "class_attributes": 40.0,
434 "interface_attributes": 0.0,
435 "classes_average": 0.5,
436 "interfaces_average": null,
437 "total": 20.0,
438 "total_attributes": 40.0,
439 "average": 0.5
440 }"###
441 );
442 },
443 );
444 }
445
446 #[test]
447 fn java_object_attributes() {
448 check_metrics::<JavaParser>(
449 "class X {
450 public Integer[] a1 = { 1 }; // +1
451 public Integer b1, b2; // +2
452 public String[] c1 = { \"Hello\" }, c2, c3 = { \"World!\" }; // +3
453 public String d1[][] = { { \"Hello\" }, { \"World!\" } }; // +1
454 public Y[] e1, e2[]; // +2
455 public Y f1[], f2[][], f3[][][]; // +3
456 Integer[] g1 = { new Integer(1) };
457 Integer h1 = new Integer(1), h2 = new Integer(2);
458 String[] i1, i2 = { \"Hello World!\" }, i3;
459 String j1 = \"Hello World!\";
460 Y[] k1[], k2;
461 Y l1[][], l2[], l3 = new Y();
462 }",
463 "foo.java",
464 |metric| {
465 insta::assert_json_snapshot!(
466 metric.npa,
467 @r###"
468 {
469 "classes": 12.0,
470 "interfaces": 0.0,
471 "class_attributes": 24.0,
472 "interface_attributes": 0.0,
473 "classes_average": 0.5,
474 "interfaces_average": null,
475 "total": 12.0,
476 "total_attributes": 24.0,
477 "average": 0.5
478 }"###
479 );
480 },
481 );
482 }
483
484 #[test]
485 fn java_generic_attributes() {
486 check_metrics::<JavaParser>(
487 "class X<T, S extends T> {
488 public T a1; // +1
489 public Entry<T, S> b1, b2[]; // +2
490 public ArrayList<T> c1, c2, c3; // +3
491 public HashMap<Long, Double> d1, d2; // +2
492 public TreeSet<String> e1; // +1
493 S f1;
494 Entry<S, T> g1[], g2;
495 ArrayList<S> h1, h2, h3;
496 HashMap<Long, Float> i1, i2;
497 TreeSet<Entry<S, T>> j1;
498 }",
499 "foo.java",
500 |metric| {
501 insta::assert_json_snapshot!(
502 metric.npa,
503 @r###"
504 {
505 "classes": 9.0,
506 "interfaces": 0.0,
507 "class_attributes": 18.0,
508 "interface_attributes": 0.0,
509 "classes_average": 0.5,
510 "interfaces_average": null,
511 "total": 9.0,
512 "total_attributes": 18.0,
513 "average": 0.5
514 }"###
515 );
516 },
517 );
518 }
519
520 #[test]
521 fn java_attribute_modifiers() {
522 check_metrics::<JavaParser>(
523 "class X {
524 public transient volatile static int a; // +1
525 transient public volatile static int b; // +1
526 transient volatile public static int c; // +1
527 transient volatile static public int d; // +1
528 public transient static final int e = 1; // +1
529 transient public static final int f = 2; // +1
530 transient static public final int g = 3; // +1
531 transient static final public int h = 4; // +1
532 protected transient volatile static int i;
533 transient volatile static protected int j;
534 private transient volatile static int k;
535 transient volatile static private int l;
536 transient volatile static int m;
537 transient static final int n = 5;
538 static public final int o = 6; // +1
539 final public int p = 7; // +1
540 }",
541 "foo.java",
542 |metric| {
543 insta::assert_json_snapshot!(
544 metric.npa,
545 @r###"
546 {
547 "classes": 10.0,
548 "interfaces": 0.0,
549 "class_attributes": 16.0,
550 "interface_attributes": 0.0,
551 "classes_average": 0.625,
552 "interfaces_average": null,
553 "total": 10.0,
554 "total_attributes": 16.0,
555 "average": 0.625
556 }"###
557 );
558 },
559 );
560 }
561
562 #[test]
563 fn java_classes() {
564 check_metrics::<JavaParser>(
565 "class X {
566 public int a; // +1
567 public boolean b; // +1
568 private char c;
569 }
570 class Y {
571 private double d;
572 private long e;
573 public float f; // +1
574 }",
575 "foo.java",
576 |metric| {
577 insta::assert_json_snapshot!(
578 metric.npa,
579 @r###"
580 {
581 "classes": 3.0,
582 "interfaces": 0.0,
583 "class_attributes": 6.0,
584 "interface_attributes": 0.0,
585 "classes_average": 0.5,
586 "interfaces_average": null,
587 "total": 3.0,
588 "total_attributes": 6.0,
589 "average": 0.5
590 }"###
591 );
592 },
593 );
594 }
595
596 #[test]
597 fn java_nested_inner_classes() {
598 check_metrics::<JavaParser>(
599 "class X {
600 public int a; // +1
601 class Y {
602 public boolean b; // +1
603 class Z {
604 public char c; // +1
605 }
606 }
607 }",
608 "foo.java",
609 |metric| {
610 insta::assert_json_snapshot!(
611 metric.npa,
612 @r###"
613 {
614 "classes": 3.0,
615 "interfaces": 0.0,
616 "class_attributes": 3.0,
617 "interface_attributes": 0.0,
618 "classes_average": 1.0,
619 "interfaces_average": null,
620 "total": 3.0,
621 "total_attributes": 3.0,
622 "average": 1.0
623 }"###
624 );
625 },
626 );
627 }
628
629 #[test]
630 fn java_local_inner_classes() {
631 check_metrics::<JavaParser>(
632 "class X {
633 public int a; // +1
634 void x() {
635 class Y {
636 public boolean b; // +1
637 void y() {
638 class Z {
639 public char c; // +1
640 void z() {}
641 }
642 }
643 }
644 }
645 }",
646 "foo.java",
647 |metric| {
648 insta::assert_json_snapshot!(
649 metric.npa,
650 @r###"
651 {
652 "classes": 3.0,
653 "interfaces": 0.0,
654 "class_attributes": 3.0,
655 "interface_attributes": 0.0,
656 "classes_average": 1.0,
657 "interfaces_average": null,
658 "total": 3.0,
659 "total_attributes": 3.0,
660 "average": 1.0
661 }"###
662 );
663 },
664 );
665 }
666
667 #[test]
668 fn java_anonymous_inner_classes() {
669 check_metrics::<JavaParser>(
670 "abstract class X {
671 public int a; // +1
672 }
673 abstract class Y {
674 boolean b;
675 }
676 class Z {
677 public char c; // +1
678 public void z(){
679 X x1 = new X() {
680 public double d; // +1
681 };
682 Y y1 = new Y() {
683 long e;
684 };
685 }
686 }",
687 "foo.java",
688 |metric| {
689 insta::assert_json_snapshot!(
690 metric.npa,
691 @r###"
692 {
693 "classes": 3.0,
694 "interfaces": 0.0,
695 "class_attributes": 5.0,
696 "interface_attributes": 0.0,
697 "classes_average": 0.6,
698 "interfaces_average": null,
699 "total": 3.0,
700 "total_attributes": 5.0,
701 "average": 0.6
702 }"###
703 );
704 },
705 );
706 }
707
708 #[test]
709 fn java_interface() {
710 check_metrics::<JavaParser>(
711 "interface X {
712 public int a = 0; // +1
713 static boolean b = false; // +1
714 final char c = ' '; // +1
715 }",
716 "foo.java",
717 |metric| {
718 insta::assert_json_snapshot!(
719 metric.npa,
720 @r###"
721 {
722 "classes": 0.0,
723 "interfaces": 3.0,
724 "class_attributes": 0.0,
725 "interface_attributes": 3.0,
726 "classes_average": null,
727 "interfaces_average": 1.0,
728 "total": 3.0,
729 "total_attributes": 3.0,
730 "average": 1.0
731 }"###
732 );
733 },
734 );
735 }
736}