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_npm: usize,
18 interface_npm: usize,
19 class_nm: usize,
20 interface_nm: usize,
21 class_npm_sum: usize,
22 interface_npm_sum: usize,
23 class_nm_sum: usize,
24 interface_nm_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("npm", 9)?;
34 st.serialize_field("classes", &self.class_npm_sum())?;
35 st.serialize_field("interfaces", &self.interface_npm_sum())?;
36 st.serialize_field("class_methods", &self.class_nm_sum())?;
37 st.serialize_field("interface_methods", &self.interface_nm_sum())?;
38 st.serialize_field("classes_average", &self.class_coa())?;
39 st.serialize_field("interfaces_average", &self.interface_coa())?;
40 st.serialize_field("total", &self.total_npm())?;
41 st.serialize_field("total_methods", &self.total_nm())?;
42 st.serialize_field("average", &self.total_coa())?;
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_methods: {}, interface_methods: {}, classes_average: {}, interfaces_average: {}, total: {}, total_methods: {}, average: {}",
52 self.class_npm_sum(),
53 self.interface_npm_sum(),
54 self.class_nm_sum(),
55 self.interface_nm_sum(),
56 self.class_coa(),
57 self.interface_coa(),
58 self.total_npm(),
59 self.total_nm(),
60 self.total_coa()
61 )
62 }
63}
64
65impl Stats {
66 pub fn merge(&mut self, other: &Stats) {
68 self.class_npm_sum += other.class_npm_sum;
69 self.interface_npm_sum += other.interface_npm_sum;
70 self.class_nm_sum += other.class_nm_sum;
71 self.interface_nm_sum += other.interface_nm_sum;
72 }
73
74 #[inline(always)]
76 pub fn class_npm(&self) -> f64 {
77 self.class_npm as f64
78 }
79
80 #[inline(always)]
82 pub fn interface_npm(&self) -> f64 {
83 self.interface_npm as f64
84 }
85
86 #[inline(always)]
88 pub fn class_nm(&self) -> f64 {
89 self.class_nm as f64
90 }
91
92 #[inline(always)]
94 pub fn interface_nm(&self) -> f64 {
95 self.interface_nm as f64
96 }
97
98 #[inline(always)]
100 pub fn class_npm_sum(&self) -> f64 {
101 self.class_npm_sum as f64
102 }
103
104 #[inline(always)]
106 pub fn interface_npm_sum(&self) -> f64 {
107 self.interface_npm_sum as f64
108 }
109
110 #[inline(always)]
112 pub fn class_nm_sum(&self) -> f64 {
113 self.class_nm_sum as f64
114 }
115
116 #[inline(always)]
118 pub fn interface_nm_sum(&self) -> f64 {
119 self.interface_nm_sum as f64
120 }
121
122 #[inline(always)]
132 pub fn class_coa(&self) -> f64 {
133 self.class_npm_sum() / self.class_nm_sum()
134 }
135
136 #[inline(always)]
146 pub fn interface_coa(&self) -> f64 {
147 if self.interface_npm_sum == self.interface_nm_sum && self.interface_npm_sum != 0 {
150 1.0
151 } else {
152 self.interface_npm_sum() / self.interface_nm_sum()
153 }
154 }
155
156 #[inline(always)]
166 pub fn total_coa(&self) -> f64 {
167 self.total_npm() / self.total_nm()
168 }
169
170 #[inline(always)]
172 pub fn total_npm(&self) -> f64 {
173 self.class_npm_sum() + self.interface_npm_sum()
174 }
175
176 #[inline(always)]
178 pub fn total_nm(&self) -> f64 {
179 self.class_nm_sum() + self.interface_nm_sum()
180 }
181
182 #[inline(always)]
185 pub(crate) fn compute_sum(&mut self) {
186 self.class_npm_sum += self.class_npm;
187 self.interface_npm_sum += self.interface_npm;
188 self.class_nm_sum += self.class_nm;
189 self.interface_nm_sum += self.interface_nm;
190 }
191
192 #[inline(always)]
194 pub(crate) fn is_disabled(&self) -> bool {
195 !self.is_class_space
196 }
197}
198
199pub trait Npm
200where
201 Self: Checker,
202{
203 fn compute(node: &Node, stats: &mut Stats);
204}
205
206impl Npm 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_nm += node
218 .children()
219 .filter(|node| Self::is_func(node))
220 .map(|method| {
221 if let Some(modifiers) = method.child(0) {
225 if matches!(modifiers.kind_id().into(), Modifiers)
227 && modifiers.first_child(|id| id == Public).is_some()
228 {
229 stats.class_npm += 1;
230 }
231 }
232 })
233 .count();
234 }
235 InterfaceBody => {
238 stats.interface_nm += node.children().filter(|node| Self::is_func(node)).count();
242 stats.interface_npm = stats.interface_nm;
243 }
244 _ => {}
245 }
246 }
247}
248
249implement_metric_trait!(
250 Npm,
251 PythonCode,
252 MozjsCode,
253 JavascriptCode,
254 TypescriptCode,
255 TsxCode,
256 RustCode,
257 CppCode,
258 PreprocCode,
259 CcommentCode,
260 KotlinCode
261);
262
263#[cfg(test)]
264mod tests {
265 use crate::tools::check_metrics;
266
267 use super::*;
268
269 #[test]
270 fn java_constructors() {
271 check_metrics::<JavaParser>(
272 "class X {
273 X() {}
274 private X(int a) {}
275 protected X(int a, int b) {}
276 public X(int a, int b, int c) {} // +1
277 }",
278 "foo.java",
279 |metric| {
280 insta::assert_json_snapshot!(
281 metric.npm,
282 @r###"
283 {
284 "classes": 1.0,
285 "interfaces": 0.0,
286 "class_methods": 4.0,
287 "interface_methods": 0.0,
288 "classes_average": 0.25,
289 "interfaces_average": null,
290 "total": 1.0,
291 "total_methods": 4.0,
292 "average": 0.25
293 }"###
294 );
295 },
296 );
297 }
298
299 #[test]
300 fn java_methods_returning_primitive_types() {
301 check_metrics::<JavaParser>(
302 "class X {
303 public byte a() {} // +1
304 public short b() {} // +1
305 public int c() {} // +1
306 public long d() {} // +1
307 public float e() {} // +1
308 public double f() {} // +1
309 public boolean g() {} // +1
310 public char h() {} // +1
311 byte i() {}
312 short j() {}
313 int k() {}
314 long l() {}
315 float m() {}
316 double n() {}
317 boolean o() {}
318 char p() {}
319 }",
320 "foo.java",
321 |metric| {
322 insta::assert_json_snapshot!(
323 metric.npm,
324 @r###"
325 {
326 "classes": 8.0,
327 "interfaces": 0.0,
328 "class_methods": 16.0,
329 "interface_methods": 0.0,
330 "classes_average": 0.5,
331 "interfaces_average": null,
332 "total": 8.0,
333 "total_methods": 16.0,
334 "average": 0.5
335 }"###
336 );
337 },
338 );
339 }
340
341 #[test]
342 fn java_methods_returning_arrays() {
343 check_metrics::<JavaParser>(
344 "class X {
345 public byte[] a() {} // +1
346 public short[] b() {} // +1
347 public int[] c() {} // +1
348 public long[] d() {} // +1
349 public float[] e() {} // +1
350 public double[] f() {} // +1
351 public boolean[] g() {} // +1
352 public char[] h() {} // +1
353 byte[] i() {}
354 short[] j() {}
355 int[] k() {}
356 long[] l() {}
357 float[] m() {}
358 double[] n() {}
359 boolean[] o() {}
360 char[] p() {}
361 }",
362 "foo.java",
363 |metric| {
364 insta::assert_json_snapshot!(
365 metric.npm,
366 @r###"
367 {
368 "classes": 8.0,
369 "interfaces": 0.0,
370 "class_methods": 16.0,
371 "interface_methods": 0.0,
372 "classes_average": 0.5,
373 "interfaces_average": null,
374 "total": 8.0,
375 "total_methods": 16.0,
376 "average": 0.5
377 }"###
378 );
379 },
380 );
381 }
382
383 #[test]
384 fn java_methods_returning_objects() {
385 check_metrics::<JavaParser>(
386 "class X {
387 public Integer[] a() {} // +1
388 public Integer b() {} // +1
389 public String[] c() {} // +1
390 public String d() {} // +1
391 public Y[] e() {} // +1
392 public Y f() {} // +1
393 Integer[] g() {}
394 Integer h() {}
395 String[] i() {}
396 String j() {}
397 Y[] k() {}
398 Y l() {}
399 }",
400 "foo.java",
401 |metric| {
402 insta::assert_json_snapshot!(
403 metric.npm,
404 @r###"
405 {
406 "classes": 6.0,
407 "interfaces": 0.0,
408 "class_methods": 12.0,
409 "interface_methods": 0.0,
410 "classes_average": 0.5,
411 "interfaces_average": null,
412 "total": 6.0,
413 "total_methods": 12.0,
414 "average": 0.5
415 }"###
416 );
417 },
418 );
419 }
420
421 #[test]
422 fn java_methods_with_generic_types() {
423 check_metrics::<JavaParser>(
424 "class X {
425 public <T, S extends T> void a(T x, S y) {} // +1
426 public <T, S> int b(T x, S y) {} // +1
427 public <T> boolean c(T x) {} // +1
428 public <T> ArrayList<T> d() {} // +1
429 public Y<String> e() {} // +1
430 <T, S extends T> void f(T x, S y) {}
431 <T, S> int g(T x, S y) {}
432 <T> boolean h(T x) {}
433 <T> ArrayList<T> i() {}
434 Y<String> j() {}
435 }",
436 "foo.java",
437 |metric| {
438 insta::assert_json_snapshot!(
439 metric.npm,
440 @r###"
441 {
442 "classes": 5.0,
443 "interfaces": 0.0,
444 "class_methods": 10.0,
445 "interface_methods": 0.0,
446 "classes_average": 0.5,
447 "interfaces_average": null,
448 "total": 5.0,
449 "total_methods": 10.0,
450 "average": 0.5
451 }"###
452 );
453 },
454 );
455 }
456
457 #[test]
458 fn java_method_modifiers() {
459 check_metrics::<JavaParser>(
460 "abstract class X {
461 public static final synchronized strictfp void a() {} // +1
462 static public final synchronized strictfp void b() {} // +1
463 static final public synchronized strictfp void c() {} // +1
464 static final synchronized public strictfp void d() {} // +1
465 static final synchronized strictfp public void e() {} // +1
466 protected static final synchronized native void f();
467 static protected final synchronized native void g();
468 static final protected synchronized native void h();
469 static final synchronized protected native void i();
470 static final synchronized native protected void j();
471 abstract public void k(); // +1
472 abstract void l();
473 }",
474 "foo.java",
475 |metric| {
476 insta::assert_json_snapshot!(
477 metric.npm,
478 @r###"
479 {
480 "classes": 6.0,
481 "interfaces": 0.0,
482 "class_methods": 12.0,
483 "interface_methods": 0.0,
484 "classes_average": 0.5,
485 "interfaces_average": null,
486 "total": 6.0,
487 "total_methods": 12.0,
488 "average": 0.5
489 }"###
490 );
491 },
492 );
493 }
494
495 #[test]
496 fn java_classes() {
497 check_metrics::<JavaParser>(
498 "class X {
499 public void a() {} // +1
500 public void b() {} // +1
501 private void c() {}
502 }
503 class Y {
504 private void d() {}
505 private void e() {}
506 public void f() {} // +1
507 }",
508 "foo.java",
509 |metric| {
510 insta::assert_json_snapshot!(
511 metric.npm,
512 @r###"
513 {
514 "classes": 3.0,
515 "interfaces": 0.0,
516 "class_methods": 6.0,
517 "interface_methods": 0.0,
518 "classes_average": 0.5,
519 "interfaces_average": null,
520 "total": 3.0,
521 "total_methods": 6.0,
522 "average": 0.5
523 }"###
524 );
525 },
526 );
527 }
528
529 #[test]
530 fn java_nested_inner_classes() {
531 check_metrics::<JavaParser>(
532 "class X {
533 public void a() {} // +1
534 class Y {
535 public void b() {} // +1
536 class Z {
537 public void c() {} // +1
538 }
539 }
540 }",
541 "foo.java",
542 |metric| {
543 insta::assert_json_snapshot!(
544 metric.npm,
545 @r###"
546 {
547 "classes": 3.0,
548 "interfaces": 0.0,
549 "class_methods": 3.0,
550 "interface_methods": 0.0,
551 "classes_average": 1.0,
552 "interfaces_average": null,
553 "total": 3.0,
554 "total_methods": 3.0,
555 "average": 1.0
556 }"###
557 );
558 },
559 );
560 }
561
562 #[test]
563 fn java_local_inner_classes() {
564 check_metrics::<JavaParser>(
565 "class X {
566 public void a() { // +1
567 class Y {
568 public void b() { // +1
569 class Z {
570 public void c() {} // +1
571 }
572 }
573 }
574 }
575 }",
576 "foo.java",
577 |metric| {
578 insta::assert_json_snapshot!(
579 metric.npm,
580 @r###"
581 {
582 "classes": 3.0,
583 "interfaces": 0.0,
584 "class_methods": 3.0,
585 "interface_methods": 0.0,
586 "classes_average": 1.0,
587 "interfaces_average": null,
588 "total": 3.0,
589 "total_methods": 3.0,
590 "average": 1.0
591 }"###
592 );
593 },
594 );
595 }
596
597 #[test]
598 fn java_anonymous_inner_classes() {
599 check_metrics::<JavaParser>(
600 "abstract class X {
601 public abstract void a(); // +1
602 }
603 abstract class Y {
604 abstract void b();
605 }
606 class Z {
607 public void c(){ // +1
608 X x = new X() {
609 @Override
610 public void a() {} // +1
611 };
612 Y y = new Y() {
613 @Override
614 void b() {}
615 };
616 }
617 }",
618 "foo.java",
619 |metric| {
620 insta::assert_json_snapshot!(
621 metric.npm,
622 @r###"
623 {
624 "classes": 3.0,
625 "interfaces": 0.0,
626 "class_methods": 5.0,
627 "interface_methods": 0.0,
628 "classes_average": 0.6,
629 "interfaces_average": null,
630 "total": 3.0,
631 "total_methods": 5.0,
632 "average": 0.6
633 }"###
634 );
635 },
636 );
637 }
638
639 #[test]
640 fn java_interface() {
641 check_metrics::<JavaParser>(
642 "interface X {
643 public int a(); // +1
644 boolean b(); // +1
645 void c(); // +1
646 }",
647 "foo.java",
648 |metric| {
649 insta::assert_json_snapshot!(
650 metric.npm,
651 @r###"
652 {
653 "classes": 0.0,
654 "interfaces": 3.0,
655 "class_methods": 0.0,
656 "interface_methods": 3.0,
657 "classes_average": null,
658 "interfaces_average": 1.0,
659 "total": 3.0,
660 "total_methods": 3.0,
661 "average": 1.0
662 }"###
663 );
664 },
665 );
666 }
667
668 #[test]
669 fn java_interfaces_and_class() {
670 check_metrics::<JavaParser>(
671 "interface X {
672 void a(); // +1
673 }
674 interface Y extends X {
675 void b(); // +1
676 void c(); // +1
677 }
678 class Z implements Y {
679 @Override
680 public void a() {} // +1
681 @Override
682 public void b() {} // +1
683 @Override
684 public void c() {} // +1
685 void d() {}
686 void e() {}
687 }",
688 "foo.java",
689 |metric| {
690 insta::assert_json_snapshot!(
691 metric.npm,
692 @r###"
693 {
694 "classes": 3.0,
695 "interfaces": 3.0,
696 "class_methods": 5.0,
697 "interface_methods": 3.0,
698 "classes_average": 0.6,
699 "interfaces_average": 1.0,
700 "total": 6.0,
701 "total_methods": 8.0,
702 "average": 0.75
703 }"###
704 );
705 },
706 );
707 }
708}