Skip to main content

rust_code_analysis_code_split/metrics/
npm.rs

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/// The `Npm` metric.
12///
13/// This metric counts the number of public methods
14/// of classes/interfaces.
15#[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    /// Merges a second `Npm` metric into the first one
67    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    /// Returns the number of class public methods in a space.
75    #[inline(always)]
76    pub fn class_npm(&self) -> f64 {
77        self.class_npm as f64
78    }
79
80    /// Returns the number of interface public methods in a space.
81    #[inline(always)]
82    pub fn interface_npm(&self) -> f64 {
83        self.interface_npm as f64
84    }
85
86    /// Returns the number of class methods in a space.
87    #[inline(always)]
88    pub fn class_nm(&self) -> f64 {
89        self.class_nm as f64
90    }
91
92    /// Returns the number of interface methods in a space.
93    #[inline(always)]
94    pub fn interface_nm(&self) -> f64 {
95        self.interface_nm as f64
96    }
97
98    /// Returns the number of class public methods sum in a space.
99    #[inline(always)]
100    pub fn class_npm_sum(&self) -> f64 {
101        self.class_npm_sum as f64
102    }
103
104    /// Returns the number of interface public methods sum in a space.
105    #[inline(always)]
106    pub fn interface_npm_sum(&self) -> f64 {
107        self.interface_npm_sum as f64
108    }
109
110    /// Returns the number of class methods sum in a space.
111    #[inline(always)]
112    pub fn class_nm_sum(&self) -> f64 {
113        self.class_nm_sum as f64
114    }
115
116    /// Returns the number of interface methods sum in a space.
117    #[inline(always)]
118    pub fn interface_nm_sum(&self) -> f64 {
119        self.interface_nm_sum as f64
120    }
121
122    /// Returns the class `Coa` metric value
123    ///
124    /// The `Class Operation Accessibility` metric value for a class
125    /// is computed by dividing the `Npm` value of the class
126    /// by the total number of methods defined in the class.
127    ///
128    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
129    /// security metric for not classified methods.
130    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
131    #[inline(always)]
132    pub fn class_coa(&self) -> f64 {
133        self.class_npm_sum() / self.class_nm_sum()
134    }
135
136    /// Returns the interface `Coa` metric value
137    ///
138    /// The `Class Operation Accessibility` metric value for an interface
139    /// is computed by dividing the `Npm` value of the interface
140    /// by the total number of methods defined in the interface.
141    ///
142    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
143    /// security metric for not classified methods.
144    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
145    #[inline(always)]
146    pub fn interface_coa(&self) -> f64 {
147        // For the Java language it's not necessary to compute the metric value
148        // The metric value in Java can only be 1.0 or f64:NAN
149        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    /// Returns the total `Coa` metric value
157    ///
158    /// The total `Class Operation Accessibility` metric value
159    /// is computed by dividing the total `Npm` value
160    /// by the total number of methods.
161    ///
162    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
163    /// security metric for not classified methods.
164    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
165    #[inline(always)]
166    pub fn total_coa(&self) -> f64 {
167        self.total_npm() / self.total_nm()
168    }
169
170    /// Returns the total number of public methods in a space.
171    #[inline(always)]
172    pub fn total_npm(&self) -> f64 {
173        self.class_npm_sum() + self.interface_npm_sum()
174    }
175
176    /// Returns the total number of methods in a space.
177    #[inline(always)]
178    pub fn total_nm(&self) -> f64 {
179        self.class_nm_sum() + self.interface_nm_sum()
180    }
181
182    // Accumulates the number of class and interface
183    // public and not public methods into the sums
184    #[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    // Checks if the `Npm` metric is disabled
193    #[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        // Enables the `Npm` metric if computing stats of a class space
211        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                        // The first child node contains the list of method modifiers
222                        // There are several modifiers that may be part of a method declaration
223                        // Source: https://docs.oracle.com/javase/tutorial/reflect/member/methodModifiers.html
224                        if let Some(modifiers) = method.child(0) {
225                            // Looks for the `public` keyword in the list of method modifiers
226                            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            // All methods in an interface are implicitly public
236            // Source: https://docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html
237            InterfaceBody => {
238                // Children nodes are filtered because Java interfaces
239                // can contain methods but also constants and nested types
240                // Source: https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html
241                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}