rust_code_analysis/metrics/
wmc.rs

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