rust_code_analysis/metrics/
npm.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::langs::*;
7use crate::node::Node;
8use crate::*;
9
10/// The `Npm` metric.
11///
12/// This metric counts the number of public methods
13/// of classes/interfaces.
14#[derive(Clone, Debug, Default)]
15pub struct Stats {
16    class_npm: usize,
17    interface_npm: usize,
18    class_nm: usize,
19    interface_nm: usize,
20    class_npm_sum: usize,
21    interface_npm_sum: usize,
22    class_nm_sum: usize,
23    interface_nm_sum: usize,
24    is_class_space: bool,
25}
26
27impl Serialize for Stats {
28    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
29    where
30        S: Serializer,
31    {
32        let mut st = serializer.serialize_struct("npm", 9)?;
33        st.serialize_field("classes", &self.class_npm_sum())?;
34        st.serialize_field("interfaces", &self.interface_npm_sum())?;
35        st.serialize_field("class_methods", &self.class_nm_sum())?;
36        st.serialize_field("interface_methods", &self.interface_nm_sum())?;
37        st.serialize_field("classes_average", &self.class_coa())?;
38        st.serialize_field("interfaces_average", &self.interface_coa())?;
39        st.serialize_field("total", &self.total_npm())?;
40        st.serialize_field("total_methods", &self.total_nm())?;
41        st.serialize_field("average", &self.total_coa())?;
42        st.end()
43    }
44}
45
46impl fmt::Display for Stats {
47    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
48        write!(
49            f,
50            "classes: {}, interfaces: {}, class_methods: {}, interface_methods: {}, classes_average: {}, interfaces_average: {}, total: {}, total_methods: {}, average: {}",
51            self.class_npm_sum(),
52            self.interface_npm_sum(),
53            self.class_nm_sum(),
54            self.interface_nm_sum(),
55            self.class_coa(),
56            self.interface_coa(),
57            self.total_npm(),
58            self.total_nm(),
59            self.total_coa()
60        )
61    }
62}
63
64impl Stats {
65    /// Merges a second `Npm` metric into the first one
66    pub fn merge(&mut self, other: &Stats) {
67        self.class_npm_sum += other.class_npm_sum;
68        self.interface_npm_sum += other.interface_npm_sum;
69        self.class_nm_sum += other.class_nm_sum;
70        self.interface_nm_sum += other.interface_nm_sum;
71    }
72
73    /// Returns the number of class public methods in a space.
74    #[inline(always)]
75    pub fn class_npm(&self) -> f64 {
76        self.class_npm as f64
77    }
78
79    /// Returns the number of interface public methods in a space.
80    #[inline(always)]
81    pub fn interface_npm(&self) -> f64 {
82        self.interface_npm as f64
83    }
84
85    /// Returns the number of class methods in a space.
86    #[inline(always)]
87    pub fn class_nm(&self) -> f64 {
88        self.class_nm as f64
89    }
90
91    /// Returns the number of interface methods in a space.
92    #[inline(always)]
93    pub fn interface_nm(&self) -> f64 {
94        self.interface_nm as f64
95    }
96
97    /// Returns the number of class public methods sum in a space.
98    #[inline(always)]
99    pub fn class_npm_sum(&self) -> f64 {
100        self.class_npm_sum as f64
101    }
102
103    /// Returns the number of interface public methods sum in a space.
104    #[inline(always)]
105    pub fn interface_npm_sum(&self) -> f64 {
106        self.interface_npm_sum as f64
107    }
108
109    /// Returns the number of class methods sum in a space.
110    #[inline(always)]
111    pub fn class_nm_sum(&self) -> f64 {
112        self.class_nm_sum as f64
113    }
114
115    /// Returns the number of interface methods sum in a space.
116    #[inline(always)]
117    pub fn interface_nm_sum(&self) -> f64 {
118        self.interface_nm_sum as f64
119    }
120
121    /// Returns the class `Coa` metric value
122    ///
123    /// The `Class Operation Accessibility` metric value for a class
124    /// is computed by dividing the `Npm` value of the class
125    /// by the total number of methods defined in the class.
126    ///
127    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
128    /// security metric for not classified methods.
129    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
130    #[inline(always)]
131    pub fn class_coa(&self) -> f64 {
132        self.class_npm_sum() / self.class_nm_sum()
133    }
134
135    /// Returns the interface `Coa` metric value
136    ///
137    /// The `Class Operation Accessibility` metric value for an interface
138    /// is computed by dividing the `Npm` value of the interface
139    /// by the total number of methods defined in the interface.
140    ///
141    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
142    /// security metric for not classified methods.
143    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
144    #[inline(always)]
145    pub fn interface_coa(&self) -> f64 {
146        // For the Java language it's not necessary to compute the metric value
147        // The metric value in Java can only be 1.0 or f64:NAN
148        if self.interface_npm_sum == self.interface_nm_sum && self.interface_npm_sum != 0 {
149            1.0
150        } else {
151            self.interface_npm_sum() / self.interface_nm_sum()
152        }
153    }
154
155    /// Returns the total `Coa` metric value
156    ///
157    /// The total `Class Operation Accessibility` metric value
158    /// is computed by dividing the total `Npm` value
159    /// by the total number of methods.
160    ///
161    /// This metric is an adaptation of the `Classified Operation Accessibility` (`COA`)
162    /// security metric for not classified methods.
163    /// Paper: <https://ieeexplore.ieee.org/abstract/document/5381538>
164    #[inline(always)]
165    pub fn total_coa(&self) -> f64 {
166        self.total_npm() / self.total_nm()
167    }
168
169    /// Returns the total number of public methods in a space.
170    #[inline(always)]
171    pub fn total_npm(&self) -> f64 {
172        self.class_npm_sum() + self.interface_npm_sum()
173    }
174
175    /// Returns the total number of methods in a space.
176    #[inline(always)]
177    pub fn total_nm(&self) -> f64 {
178        self.class_nm_sum() + self.interface_nm_sum()
179    }
180
181    // Accumulates the number of class and interface
182    // public and not public methods into the sums
183    #[inline(always)]
184    pub(crate) fn compute_sum(&mut self) {
185        self.class_npm_sum += self.class_npm;
186        self.interface_npm_sum += self.interface_npm;
187        self.class_nm_sum += self.class_nm;
188        self.interface_nm_sum += self.interface_nm;
189    }
190
191    // Checks if the `Npm` metric is disabled
192    #[inline(always)]
193    pub(crate) fn is_disabled(&self) -> bool {
194        !self.is_class_space
195    }
196}
197
198#[doc(hidden)]
199pub trait Npm
200where
201    Self: Checker,
202{
203    fn compute(_node: &Node, _stats: &mut Stats) {}
204}
205
206impl Npm for PythonCode {}
207impl Npm for MozjsCode {}
208impl Npm for JavascriptCode {}
209impl Npm for TypescriptCode {}
210impl Npm for TsxCode {}
211impl Npm for RustCode {}
212impl Npm for CppCode {}
213impl Npm for PreprocCode {}
214impl Npm for CcommentCode {}
215
216impl Npm for JavaCode {
217    fn compute(node: &Node, stats: &mut Stats) {
218        use Java::*;
219
220        // Enables the `Npm` metric if computing stats of a class space
221        if Self::is_func_space(node) && stats.is_disabled() {
222            stats.is_class_space = true;
223        }
224
225        match node.object().kind_id().into() {
226            ClassBody => {
227                stats.class_nm += node
228                    .children()
229                    .filter(|node| Self::is_func(node))
230                    .map(|method| {
231                        // The first child node contains the list of method modifiers
232                        // There are several modifiers that may be part of a method declaration
233                        // Source: https://docs.oracle.com/javase/tutorial/reflect/member/methodModifiers.html
234                        if let Some(modifiers) = method.object().child(0) {
235                            // Looks for the `public` keyword in the list of method modifiers
236                            if matches!(modifiers.kind_id().into(), Modifiers)
237                                && Node::new(modifiers)
238                                    .first_child(|id| id == Public)
239                                    .is_some()
240                            {
241                                stats.class_npm += 1;
242                            }
243                        }
244                    })
245                    .count();
246            }
247            // All methods in an interface are implicitly public
248            // Source: https://docs.oracle.com/javase/tutorial/java/IandI/interfaceDef.html
249            InterfaceBody => {
250                // Children nodes are filtered because Java interfaces
251                // can contain methods but also constants and nested types
252                // Source: https://docs.oracle.com/javase/tutorial/java/IandI/createinterface.html
253                stats.interface_nm += node.children().filter(|node| Self::is_func(node)).count();
254                stats.interface_npm = stats.interface_nm;
255            }
256            _ => {}
257        }
258    }
259}
260
261#[cfg(test)]
262mod tests {
263    use std::path::PathBuf;
264
265    use super::*;
266
267    #[test]
268    fn java_constructors() {
269        check_metrics!(
270            "class X {
271                X() {}
272                private X(int a) {}
273                protected X(int a, int b) {}
274                public X(int a, int b, int c) {}    // +1
275            }",
276            "foo.java",
277            JavaParser,
278            npm,
279            [
280                (class_npm_sum, 1, usize),
281                (interface_npm_sum, 0, usize),
282                (class_nm_sum, 4, usize),
283                (interface_nm_sum, 0, usize),
284                (total_npm, 1, usize),
285                (total_nm, 4, usize)
286            ],
287            [
288                (class_coa, 0.25),
289                (interface_coa, f64::NAN),
290                (total_coa, 0.25)
291            ]
292        );
293    }
294
295    #[test]
296    fn java_methods_returning_primitive_types() {
297        check_metrics!(
298            "class X {
299                public byte a() {}      // +1
300                public short b() {}     // +1
301                public int c() {}       // +1
302                public long d() {}      // +1
303                public float e() {}     // +1
304                public double f() {}    // +1
305                public boolean g() {}   // +1
306                public char h() {}      // +1
307                byte i() {}
308                short j() {}
309                int k() {}
310                long l() {}
311                float m() {}
312                double n() {}
313                boolean o() {}
314                char p() {}
315            }",
316            "foo.java",
317            JavaParser,
318            npm,
319            [
320                (class_npm_sum, 8, usize),
321                (interface_npm_sum, 0, usize),
322                (class_nm_sum, 16, usize),
323                (interface_nm_sum, 0, usize),
324                (total_npm, 8, usize),
325                (total_nm, 16, usize)
326            ],
327            [
328                (class_coa, 0.5),
329                (interface_coa, f64::NAN),
330                (total_coa, 0.5)
331            ]
332        );
333    }
334
335    #[test]
336    fn java_methods_returning_arrays() {
337        check_metrics!(
338            "class X {
339                public byte[] a() {}    // +1
340                public short[] b() {}   // +1
341                public int[] c() {}     // +1
342                public long[] d() {}    // +1
343                public float[] e() {}   // +1
344                public double[] f() {}  // +1
345                public boolean[] g() {} // +1
346                public char[] h() {}    // +1
347                byte[] i() {}
348                short[] j() {}
349                int[] k() {}
350                long[] l() {}
351                float[] m() {}
352                double[] n() {}
353                boolean[] o() {}
354                char[] p() {}
355            }",
356            "foo.java",
357            JavaParser,
358            npm,
359            [
360                (class_npm_sum, 8, usize),
361                (interface_npm_sum, 0, usize),
362                (class_nm_sum, 16, usize),
363                (interface_nm_sum, 0, usize),
364                (total_npm, 8, usize),
365                (total_nm, 16, usize)
366            ],
367            [
368                (class_coa, 0.5),
369                (interface_coa, f64::NAN),
370                (total_coa, 0.5)
371            ]
372        );
373    }
374
375    #[test]
376    fn java_methods_returning_objects() {
377        check_metrics!(
378            "class X {
379                public Integer[] a() {} // +1
380                public Integer b() {}   // +1
381                public String[] c() {}  // +1
382                public String d() {}    // +1
383                public Y[] e() {}       // +1
384                public Y f() {}         // +1
385                Integer[] g() {}
386                Integer h() {}
387                String[] i() {}
388                String j() {}
389                Y[] k() {}
390                Y l() {}
391            }",
392            "foo.java",
393            JavaParser,
394            npm,
395            [
396                (class_npm_sum, 6, usize),
397                (interface_npm_sum, 0, usize),
398                (class_nm_sum, 12, usize),
399                (interface_nm_sum, 0, usize),
400                (total_npm, 6, usize),
401                (total_nm, 12, usize)
402            ],
403            [
404                (class_coa, 0.5),
405                (interface_coa, f64::NAN),
406                (total_coa, 0.5)
407            ]
408        );
409    }
410
411    #[test]
412    fn java_methods_with_generic_types() {
413        check_metrics!(
414            "class X {
415                public <T, S extends T> void a(T x, S y) {} // +1
416                public <T, S> int b(T x, S y) {}            // +1
417                public <T> boolean c(T x) {}                // +1
418                public <T> ArrayList<T> d() {}              // +1
419                public Y<String> e() {}                     // +1
420                <T, S extends T> void f(T x, S y) {}
421                <T, S> int g(T x, S y) {}
422                <T> boolean h(T x) {}
423                <T> ArrayList<T> i() {}
424                Y<String> j() {}
425            }",
426            "foo.java",
427            JavaParser,
428            npm,
429            [
430                (class_npm_sum, 5, usize),
431                (interface_npm_sum, 0, usize),
432                (class_nm_sum, 10, usize),
433                (interface_nm_sum, 0, usize),
434                (total_npm, 5, usize),
435                (total_nm, 10, usize)
436            ],
437            [
438                (class_coa, 0.5),
439                (interface_coa, f64::NAN),
440                (total_coa, 0.5)
441            ]
442        );
443    }
444
445    #[test]
446    fn java_method_modifiers() {
447        check_metrics!(
448            "abstract class X {
449                public static final synchronized strictfp void a() {}   // +1
450                static public final synchronized strictfp void b() {}   // +1
451                static final public synchronized strictfp void c() {}   // +1
452                static final synchronized public strictfp void d() {}   // +1
453                static final synchronized strictfp public void e() {}   // +1
454                protected static final synchronized native void f();
455                static protected final synchronized native void g();
456                static final protected synchronized native void h();
457                static final synchronized protected native void i();
458                static final synchronized native protected void j();
459                abstract public void k();                               // +1
460                abstract void l();
461            }",
462            "foo.java",
463            JavaParser,
464            npm,
465            [
466                (class_npm_sum, 6, usize),
467                (interface_npm_sum, 0, usize),
468                (class_nm_sum, 12, usize),
469                (interface_nm_sum, 0, usize),
470                (total_npm, 6, usize),
471                (total_nm, 12, usize)
472            ],
473            [
474                (class_coa, 0.5),
475                (interface_coa, f64::NAN),
476                (total_coa, 0.5)
477            ]
478        );
479    }
480
481    #[test]
482    fn java_classes() {
483        check_metrics!(
484            "class X {
485                public void a() {}  // +1
486                public void b() {}  // +1
487                private void c() {}
488            }
489            class Y {
490                private void d() {}
491                private void e() {}
492                public void f() {}  // +1
493            }",
494            "foo.java",
495            JavaParser,
496            npm,
497            [
498                (class_npm_sum, 3, usize),
499                (interface_npm_sum, 0, usize),
500                (class_nm_sum, 6, usize),
501                (interface_nm_sum, 0, usize),
502                (total_npm, 3, usize),
503                (total_nm, 6, usize)
504            ],
505            [
506                (class_coa, 0.5),
507                (interface_coa, f64::NAN),
508                (total_coa, 0.5)
509            ]
510        );
511    }
512
513    #[test]
514    fn java_nested_inner_classes() {
515        check_metrics!(
516            "class X {
517                public void a() {}          // +1
518                class Y {
519                    public void b() {}      // +1
520                    class Z {
521                        public void c() {}  // +1
522                    }
523                }
524            }",
525            "foo.java",
526            JavaParser,
527            npm,
528            [
529                (class_npm_sum, 3, usize),
530                (interface_npm_sum, 0, usize),
531                (class_nm_sum, 3, usize),
532                (interface_nm_sum, 0, usize),
533                (total_npm, 3, usize),
534                (total_nm, 3, usize)
535            ],
536            [
537                (class_coa, 1.0),
538                (interface_coa, f64::NAN),
539                (total_coa, 1.0)
540            ]
541        );
542    }
543
544    #[test]
545    fn java_local_inner_classes() {
546        check_metrics!(
547            "class X {
548                public void a() {                   // +1
549                    class Y {
550                        public void b() {           // +1
551                            class Z {
552                                public void c() {}  // +1
553                            }
554                        }
555                    }
556                }
557            }",
558            "foo.java",
559            JavaParser,
560            npm,
561            [
562                (class_npm_sum, 3, usize),
563                (interface_npm_sum, 0, usize),
564                (class_nm_sum, 3, usize),
565                (interface_nm_sum, 0, usize),
566                (total_npm, 3, usize),
567                (total_nm, 3, usize)
568            ],
569            [
570                (class_coa, 1.0),
571                (interface_coa, f64::NAN),
572                (total_coa, 1.0)
573            ]
574        );
575    }
576
577    #[test]
578    fn java_anonymous_inner_classes() {
579        check_metrics!(
580            "abstract class X {
581                public abstract void a();   // +1
582            }
583            abstract class Y {
584                abstract void b();
585            }
586            class Z {
587                public void c(){            // +1
588                    X x = new X() {
589                        @Override
590                        public void a() {}  // +1
591                    };
592                    Y y = new Y() {
593                        @Override
594                        void b() {}
595                    };
596                }
597            }",
598            "foo.java",
599            JavaParser,
600            npm,
601            [
602                (class_npm_sum, 3, usize),
603                (interface_npm_sum, 0, usize),
604                (class_nm_sum, 5, usize),
605                (interface_nm_sum, 0, usize),
606                (total_npm, 3, usize),
607                (total_nm, 5, usize)
608            ],
609            [
610                (class_coa, 0.6),
611                (interface_coa, f64::NAN),
612                (total_coa, 0.6)
613            ]
614        );
615    }
616
617    #[test]
618    fn java_interface() {
619        check_metrics!(
620            "interface X {
621                public int a(); // +1
622                boolean b();    // +1
623                void c();       // +1
624            }",
625            "foo.java",
626            JavaParser,
627            npm,
628            [
629                (class_npm_sum, 0, usize),
630                (interface_npm_sum, 3, usize),
631                (class_nm_sum, 0, usize),
632                (interface_nm_sum, 3, usize),
633                (total_npm, 3, usize),
634                (total_nm, 3, usize)
635            ],
636            [
637                (class_coa, f64::NAN),
638                (interface_coa, 1.0),
639                (total_coa, 1.0)
640            ]
641        );
642    }
643
644    #[test]
645    fn java_interfaces_and_class() {
646        check_metrics!(
647            "interface X {
648                void a();           // +1
649            }
650            interface Y extends X {
651                void b();           // +1
652                void c();           // +1
653            }
654            class Z implements Y {
655                @Override
656                public void a() {}  // +1
657                @Override
658                public void b() {}  // +1
659                @Override
660                public void c() {}  // +1
661                void d() {}
662                void e() {}
663            }",
664            "foo.java",
665            JavaParser,
666            npm,
667            [
668                (class_npm_sum, 3, usize),
669                (interface_npm_sum, 3, usize),
670                (class_nm_sum, 5, usize),
671                (interface_nm_sum, 3, usize),
672                (total_npm, 6, usize),
673                (total_nm, 8, usize)
674            ],
675            [(class_coa, 0.6), (interface_coa, 1.0), (total_coa, 0.75)]
676        );
677    }
678}