Skip to main content

rust_code_analysis_code_split/metrics/
wmc.rs

1use serde::Serialize;
2use serde::ser::{SerializeStruct, Serializer};
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::macros::implement_metric_trait;
7use crate::*;
8
9// FIX ME: New Java switches are not correctly recognised by tree-sitter-java version 0.19.0
10// However, the issue has already been addressed and resolved upstream on the tree-sitter-java GitHub repository
11// Upstream issue: https://github.com/tree-sitter/tree-sitter-java/issues/69
12// Upstream PR which resolves the issue: https://github.com/tree-sitter/tree-sitter-java/pull/78
13
14/// The `Wmc` metric.
15///
16/// This metric sums the cyclomatic complexities of all the methods defined in a class.
17/// The `Wmc` (Weighted Methods per Class) is an object-oriented metric for classes.
18///
19/// Original paper and definition:
20/// <https://www.researchgate.net/publication/3187649_Kemerer_CF_A_metric_suite_for_object_oriented_design_IEEE_Trans_Softw_Eng_206_476-493>
21#[derive(Debug, Clone, Default)]
22pub struct Stats {
23    cyclomatic: f64,
24    class_wmc: f64,
25    interface_wmc: f64,
26    class_wmc_sum: f64,
27    interface_wmc_sum: f64,
28    space_kind: SpaceKind,
29}
30
31impl Serialize for Stats {
32    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
33    where
34        S: Serializer,
35    {
36        let mut st = serializer.serialize_struct("wmc", 3)?;
37        st.serialize_field("classes", &self.class_wmc_sum())?;
38        st.serialize_field("interfaces", &self.interface_wmc_sum())?;
39        st.serialize_field("total", &self.total_wmc())?;
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            "classes: {}, interfaces: {}, total: {}",
49            self.class_wmc_sum(),
50            self.interface_wmc_sum(),
51            self.total_wmc()
52        )
53    }
54}
55
56impl Stats {
57    /// Merges a second `Wmc` metric into the first one
58    pub fn merge(&mut self, other: &Stats) {
59        use SpaceKind::*;
60
61        // Merges the cyclomatic complexity of a method
62        // into the `Wmc` metric value of a class or interface
63        if let Function = other.space_kind {
64            match self.space_kind {
65                Class => self.class_wmc += other.cyclomatic,
66                Interface => self.interface_wmc += other.cyclomatic,
67                _ => {}
68            }
69        }
70
71        self.class_wmc_sum += other.class_wmc_sum;
72        self.interface_wmc_sum += other.interface_wmc_sum;
73    }
74
75    /// Returns the `Wmc` metric value of the classes in a space.
76    #[inline(always)]
77    pub fn class_wmc(&self) -> f64 {
78        self.class_wmc
79    }
80
81    /// Returns the `Wmc` metric value of the interfaces in a space.
82    #[inline(always)]
83    pub fn interface_wmc(&self) -> f64 {
84        self.interface_wmc
85    }
86
87    /// Returns the sum of the `Wmc` metric values of the classes in a space.
88    #[inline(always)]
89    pub fn class_wmc_sum(&self) -> f64 {
90        self.class_wmc_sum
91    }
92
93    /// Returns the sum of the `Wmc` metric values of the interfaces in a space.
94    #[inline(always)]
95    pub fn interface_wmc_sum(&self) -> f64 {
96        self.interface_wmc_sum
97    }
98
99    /// Returns the total `Wmc` metric value in a space.
100    #[inline(always)]
101    pub fn total_wmc(&self) -> f64 {
102        self.class_wmc_sum() + self.interface_wmc_sum()
103    }
104
105    // Accumulates the `Wmc` metric values
106    // of classes and interfaces into the sums
107    #[inline(always)]
108    pub(crate) fn compute_sum(&mut self) {
109        self.class_wmc_sum += self.class_wmc;
110        self.interface_wmc_sum += self.interface_wmc;
111    }
112
113    // Checks if the `Wmc` metric is disabled
114    #[inline(always)]
115    pub(crate) fn is_disabled(&self) -> bool {
116        matches!(self.space_kind, SpaceKind::Function | SpaceKind::Unknown)
117    }
118}
119
120pub trait Wmc
121where
122    Self: Checker,
123{
124    fn compute(space_kind: SpaceKind, cyclomatic: &cyclomatic::Stats, stats: &mut Stats);
125}
126
127impl Wmc for JavaCode {
128    fn compute(space_kind: SpaceKind, cyclomatic: &cyclomatic::Stats, stats: &mut Stats) {
129        use SpaceKind::*;
130
131        if let Unit | Class | Interface | Function = space_kind {
132            if stats.space_kind == Unknown {
133                stats.space_kind = space_kind;
134            }
135            if space_kind == Function {
136                // Saves the cyclomatic complexity of the method
137                stats.cyclomatic = cyclomatic.cyclomatic_sum();
138            }
139        }
140    }
141}
142
143implement_metric_trait!(
144    Wmc,
145    PythonCode,
146    MozjsCode,
147    JavascriptCode,
148    TypescriptCode,
149    TsxCode,
150    RustCode,
151    CppCode,
152    PreprocCode,
153    CcommentCode,
154    KotlinCode
155);
156
157#[cfg(test)]
158mod tests {
159    use crate::tools::check_metrics;
160
161    use super::*;
162
163    #[test]
164    fn java_single_class() {
165        check_metrics::<JavaParser>(
166            "public class Example { // wmc = 13
167
168                public boolean m1(boolean a, boolean b) { // +1
169                    boolean r = false;
170                    if (a && b == a || b) { // +3
171                        r = true;
172                    }
173                    return r;
174                }
175
176                public boolean m2(int n) { // +1
177                    for (int i = 0; i < n; i++) { // +1
178                        int j = n;
179                        while (j > i) { // +1
180                            j--;
181                        }
182                    }
183                    return (n % 2 == 0) ? true : false; // +1
184                }
185
186                public int m3(int x, int y, int z) { // +1
187                    int ret;
188                    try {
189                        z = x/y + y/x;
190                    } catch (ArithmeticException e) { // +1
191                        z = (x == 0) ? -1 : -2; // +1
192                    }
193                    switch (z) {
194                        case -1: // +1
195                            ret = y * y;
196                            break;
197                        case -2: // +1
198                            ret = x * x;
199                            break;
200                        default:
201                            ret = x + y;
202                    }
203                    return ret;
204                }
205            }",
206            "foo.java",
207            |metric| {
208                // 1 class
209                insta::assert_json_snapshot!(
210                    metric.wmc,
211                    @r###"
212                    {
213                      "classes": 13.0,
214                      "interfaces": 0.0,
215                      "total": 13.0
216                    }"###
217                );
218            },
219        );
220    }
221
222    // Constructors are considered as methods
223    // Reference: https://pdepend.org/documentation/software-metrics/weighted-method-count.html
224    #[test]
225    fn java_multiple_classes() {
226        check_metrics::<JavaParser>(
227            "public class MainClass { // wmc = 3
228                private int a;
229                public MainClass() { // +1
230                    a = 0;
231                }
232                public void setA(int n) { // +1
233                    a = n;
234                }
235                public int getA() { // +1
236                    return a;
237                }
238            }
239
240            class TopLevelClass { // wmc = 2
241                private int b;
242                public TopLevelClass() { // +1
243                    b = 0;
244                }
245                public int getB() { // +1
246                    return b;
247                }
248            }",
249            "foo.java",
250            |metric| {
251                // 2 classes (3 + 2)
252                insta::assert_json_snapshot!(
253                    metric.wmc,
254                    @r###"
255                    {
256                      "classes": 5.0,
257                      "interfaces": 0.0,
258                      "total": 5.0
259                    }"###
260                );
261            },
262        );
263    }
264
265    #[test]
266    fn java_static_nested_class() {
267        check_metrics::<JavaParser>(
268            "public class TopLevelClass { // wmc = 0
269                public static class StaticNestedClass { // wmc = 1
270                    private void m() { // +1
271                        System.out.println(\"Test\");
272                    }
273                }
274            }",
275            "foo.java",
276            |metric| {
277                // 2 classes (0 + 2)
278                insta::assert_json_snapshot!(
279                    metric.wmc,
280                    @r###"
281                    {
282                      "classes": 1.0,
283                      "interfaces": 0.0,
284                      "total": 1.0
285                    }"###
286                );
287            },
288        );
289    }
290
291    #[test]
292    fn java_nested_inner_classes() {
293        check_metrics::<JavaParser>(
294            "public class TopLevelClass { // wmc = 2
295                private int a;
296
297                class InnerClassBefore { // wmc = 1
298                    private boolean b = (a % 2 == 0) ? true : false;
299                    public boolean getB() { // +1
300                        return b;
301                    }
302                }
303
304                public TopLevelClass(int n) { // +1
305                    if (a != n) { // +1
306                        a = n;
307                    }
308                }
309
310                class InnerClassAfter { // wmc = 2
311                    private int c = a;
312
313                    public int getC() { // +1
314                        return c;
315                    }
316                    public void setC(int n) { // +1
317                        c = n;
318                    }
319
320                    class InnerClass1 { // wmc = 1
321                        private int p1;
322                        class InnerClass2 { // wmc = 1
323                            private int p2;
324                            public int getP2() { // +1
325                                return p2;
326                            }
327                            class InnerClass3 { // wmc = 2
328                                private int p3;
329                                public int getP3() { // +1
330                                    return p3;
331                                }
332                                public void setP3(int n) { // +1
333                                    p3 = n;
334                                }
335                            }
336                        }
337                        public void setP1(int n) { // +1
338                            p1 = n;
339                        }
340                    }
341                }
342            }",
343            "foo.java",
344            |metric| {
345                // 6 classes (2 + 1 + 2 + 1 + 1 + 2)
346                insta::assert_json_snapshot!(
347                    metric.wmc,
348                    @r###"
349                    {
350                      "classes": 9.0,
351                      "interfaces": 0.0,
352                      "total": 9.0
353                    }"###
354                );
355            },
356        );
357    }
358
359    #[test]
360    fn java_local_inner_class() {
361        check_metrics::<JavaParser>(
362            "import java.util.LinkedList;
363            import java.util.List;
364
365            public final class FinalClass { // wmc = 5
366                private int a = 1;
367                public void test() { // +1
368                    final List<String> localList = new LinkedList<String>();
369
370                    class LocalInnerClass { // +1, wmc = 2
371                        private int b = (a == 1) ? 1 : 0; // +1
372                        public void print() { // +1
373                            for ( String s : localList ) { // +1
374                                System.out.println(s);
375                            }
376                        }
377                    }
378                }
379            }",
380            "foo.java",
381            |metric| {
382                // 2 classes (5 + 2)
383                insta::assert_json_snapshot!(
384                    metric.wmc,
385                    @r###"
386                    {
387                      "classes": 7.0,
388                      "interfaces": 0.0,
389                      "total": 7.0
390                    }"###
391                );
392            },
393        );
394    }
395
396    #[test]
397    fn java_anonymous_inner_class() {
398        check_metrics::<JavaParser>(
399            "abstract class AbstractClass { // wmc = 1
400                abstract void m1(); // +1
401            }
402            public class TopLevelClass{ // wmc = 3
403                public void m(){ // +1
404                    AbstractClass ac1 = new AbstractClass() {
405                        void m1() { // +1
406                            for (int i = 0; i < 5; i++) { // +1
407                                System.out.println(\"Test 1: \" + i);
408                            }
409                        }
410                    };
411                    ac1.m1();
412                }
413            }",
414            "foo.java",
415            |metric| {
416                // 2 classes (1 + 3)
417                insta::assert_json_snapshot!(
418                    metric.wmc,
419                    @r###"
420                    {
421                      "classes": 4.0,
422                      "interfaces": 0.0,
423                      "total": 4.0
424                    }"###
425                );
426            },
427        );
428    }
429
430    #[test]
431    fn java_nested_anonymous_inner_classes() {
432        check_metrics::<JavaParser>(
433            "abstract class AbstractClass{ // wmc = 2
434                abstract void m1(); // +1
435                abstract void m2(); // +1
436            }
437            public class TopLevelClass{ // wmc = 6
438                public void m(){ // +1
439
440                    AbstractClass ac1 = new AbstractClass() {
441                        void m1() { // +1
442                            for (int i = 0; i < 5; i++) { // +1
443                                System.out.println(\"Test 1: \" + i);
444                            }
445                        }
446                        void m2() { // +1
447                            AbstractClass ac2 = new AbstractClass() {
448                                void m1() { // +1
449                                    System.out.println(\"Test A\");
450                                }
451                                void m2() { // +1
452                                    System.out.println(\"Test B\");
453                                }
454                            };
455                            ac2.m2();
456                            System.out.println(\"Test 2\");
457                        }
458                    };
459                    ac1.m1();
460                }
461            }",
462            "foo.java",
463            |metric| {
464                // 2 classes (2 + 6)
465                insta::assert_json_snapshot!(
466                    metric.wmc,
467                    @r###"
468                    {
469                      "classes": 8.0,
470                      "interfaces": 0.0,
471                      "total": 8.0
472                    }"###
473                );
474            },
475        );
476    }
477
478    #[test]
479    fn java_lambda_expression() {
480        check_metrics::<JavaParser>(
481            "import java.util.ArrayList;
482
483            public class TopLevelClass { // wmc = 2
484                private ArrayList<Integer> numbers;
485
486                public void m1() { // +1
487                    numbers = new ArrayList<Integer>();
488                    numbers.add(1);
489                    numbers.add(2);
490                    numbers.add(3);
491                }
492
493                public void m2() { // +1
494                    numbers.forEach( (n) -> { System.out.println(n); } );
495                }
496            }",
497            "foo.java",
498            |metric| {
499                // 1 class
500                insta::assert_json_snapshot!(
501                    metric.wmc,
502                    @r###"
503                    {
504                      "classes": 2.0,
505                      "interfaces": 0.0,
506                      "total": 2.0
507                    }"###
508                );
509            },
510        );
511    }
512
513    #[test]
514    fn java_single_interface() {
515        check_metrics::<JavaParser>(
516            "interface Example { // wmc = 6
517                default boolean m1(boolean a, boolean b) { // +1
518                    return (a && b == a || b); // +2
519                }
520                default int m2(int n) { // +1
521                    return (n != 0) ? 1/n : n; // +1
522                };
523                void m3(); // +1
524            }",
525            "foo.java",
526            |metric| {
527                // 1 interface
528                insta::assert_json_snapshot!(
529                    metric.wmc,
530                    @r###"
531                    {
532                      "classes": 0.0,
533                      "interfaces": 6.0,
534                      "total": 6.0
535                    }"###
536                );
537            },
538        );
539    }
540
541    #[test]
542    fn java_multiple_interfaces() {
543        check_metrics::<JavaParser>(
544            "interface FirstInterface { // wmc = 1
545                int a = 0;
546                default int getA() { // +1
547                    return a;
548                }
549            }
550
551            interface SecondInterface { // wmc = 2
552                void setB(int n); // +1
553                int getB(); // +1
554            }",
555            "foo.java",
556            |metric| {
557                // 2 interfaces (1 + 2)
558                insta::assert_json_snapshot!(
559                    metric.wmc,
560                    @r###"
561                    {
562                      "classes": 0.0,
563                      "interfaces": 3.0,
564                      "total": 3.0
565                    }"###
566                );
567            },
568        );
569    }
570
571    #[test]
572    fn java_nested_inner_interfaces() {
573        check_metrics::<JavaParser>(
574            "interface TopLevelInterface { // wmc = 1
575                interface InnerInterfaceBefore { // wmc = 1
576                    void m1(); // +1
577                }
578
579                void m2(); // +1
580
581                interface InnerInterfaceAfter { // wmc = 2
582                    void m3(); // +1
583                    interface InnerInterface { // wmc = 1
584                        void m4(); // +1
585                    }
586                    void m5(); // +1
587                }
588            }",
589            "foo.java",
590            |metric| {
591                // 4 interfaces (1 + 1 + 2 + 1)
592                insta::assert_json_snapshot!(
593                    metric.wmc,
594                    @r###"
595                    {
596                      "classes": 0.0,
597                      "interfaces": 5.0,
598                      "total": 5.0
599                    }"###
600                );
601            },
602        );
603    }
604
605    #[test]
606    fn java_class_in_interface() {
607        check_metrics::<JavaParser>(
608            "interface TopLevelInterface { // wmc = 2
609                int getA(); // +1
610                boolean getB(); // +1
611
612                class InnerClass { // wmc = 2
613                    float c;
614                    double d;
615                    float getC() { // +1
616                        return c;
617                    }
618                    double getD() { // +1
619                        return d;
620                    }
621                }
622            }",
623            "foo.java",
624            |metric| {
625                // 1 class 1 interface
626                insta::assert_json_snapshot!(
627                    metric.wmc,
628                    @r###"
629                    {
630                      "classes": 2.0,
631                      "interfaces": 2.0,
632                      "total": 4.0
633                    }"###
634                );
635            },
636        );
637    }
638
639    #[test]
640    fn java_interface_in_class() {
641        check_metrics::<JavaParser>(
642            "class TopLevelClass { // wmc = 2
643                int a;
644                boolean b;
645                int getA() { // +1
646                    return a;
647                }
648                boolean getB() { // +1
649                    return b;
650                }
651
652                interface InnerInterface { // wmc = 2
653                    float getC(); // +1
654                    double getD(); // +1
655                }
656            }",
657            "foo.java",
658            |metric| {
659                // 1 class 1 interface
660                insta::assert_json_snapshot!(
661                    metric.wmc,
662                    @r###"
663                    {
664                      "classes": 2.0,
665                      "interfaces": 2.0,
666                      "total": 4.0
667                    }"###
668                );
669            },
670        );
671    }
672}