rust_code_analysis/metrics/
nom.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use crate::checker::Checker;
6
7use crate::*;
8
9/// The `Nom` metric suite.
10#[derive(Clone, Debug)]
11pub struct Stats {
12    functions: usize,
13    closures: usize,
14    functions_sum: usize,
15    closures_sum: usize,
16    functions_min: usize,
17    functions_max: usize,
18    closures_min: usize,
19    closures_max: usize,
20    space_count: usize,
21}
22impl Default for Stats {
23    fn default() -> Self {
24        Self {
25            functions: 0,
26            closures: 0,
27            functions_sum: 0,
28            closures_sum: 0,
29            functions_min: usize::MAX,
30            functions_max: 0,
31            closures_min: usize::MAX,
32            closures_max: 0,
33            space_count: 1,
34        }
35    }
36}
37impl Serialize for Stats {
38    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
39    where
40        S: Serializer,
41    {
42        let mut st = serializer.serialize_struct("nom", 10)?;
43        st.serialize_field("functions", &self.functions_sum())?;
44        st.serialize_field("closures", &self.closures_sum())?;
45        st.serialize_field("functions_average", &self.functions_average())?;
46        st.serialize_field("closures_average", &self.closures_average())?;
47        st.serialize_field("total", &self.total())?;
48        st.serialize_field("average", &self.average())?;
49        st.serialize_field("functions_min", &self.functions_min())?;
50        st.serialize_field("functions_max", &self.functions_max())?;
51        st.serialize_field("closures_min", &self.closures_min())?;
52        st.serialize_field("closures_max", &self.closures_max())?;
53        st.end()
54    }
55}
56
57impl fmt::Display for Stats {
58    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
59        write!(
60            f,
61            "functions: {}, \
62             closures: {}, \
63             functions_average: {}, \
64             closures_average: {}, \
65             total: {} \
66             average: {} \
67             functions_min: {} \
68             functions_max: {} \
69             closures_min: {} \
70             closures_max: {}",
71            self.functions_sum(),
72            self.closures_sum(),
73            self.functions_average(),
74            self.closures_average(),
75            self.total(),
76            self.average(),
77            self.functions_min(),
78            self.functions_max(),
79            self.closures_min(),
80            self.closures_max(),
81        )
82    }
83}
84
85impl Stats {
86    /// Merges a second `Nom` metric suite into the first one
87    pub fn merge(&mut self, other: &Stats) {
88        self.functions_min = self.functions_min.min(other.functions_min);
89        self.functions_max = self.functions_max.max(other.functions_max);
90        self.closures_min = self.closures_min.min(other.closures_min);
91        self.closures_max = self.closures_max.max(other.closures_max);
92        self.functions_sum += other.functions_sum;
93        self.closures_sum += other.closures_sum;
94        self.space_count += other.space_count;
95    }
96
97    /// Counts the number of function definitions in a scope
98    #[inline(always)]
99    pub fn functions(&self) -> f64 {
100        // Only function definitions are considered, not general declarations
101        self.functions as f64
102    }
103
104    /// Counts the number of closures in a scope
105    #[inline(always)]
106    pub fn closures(&self) -> f64 {
107        self.closures as f64
108    }
109
110    /// Return the sum metric for functions
111    #[inline(always)]
112    pub fn functions_sum(&self) -> f64 {
113        // Only function definitions are considered, not general declarations
114        self.functions_sum as f64
115    }
116
117    /// Return the sum metric for closures
118    #[inline(always)]
119    pub fn closures_sum(&self) -> f64 {
120        self.closures_sum as f64
121    }
122
123    /// Returns the average number of function definitions over all spaces
124    #[inline(always)]
125    pub fn functions_average(&self) -> f64 {
126        self.functions_sum() / self.space_count as f64
127    }
128
129    /// Returns the average number of closures over all spaces
130    #[inline(always)]
131    pub fn closures_average(&self) -> f64 {
132        self.closures_sum() / self.space_count as f64
133    }
134
135    /// Returns the average number of function definitions and closures over all spaces
136    #[inline(always)]
137    pub fn average(&self) -> f64 {
138        self.total() / self.space_count as f64
139    }
140
141    /// Counts the number of function definitions in a scope
142    #[inline(always)]
143    pub fn functions_min(&self) -> f64 {
144        // Only function definitions are considered, not general declarations
145        self.functions_min as f64
146    }
147
148    /// Counts the number of closures in a scope
149    #[inline(always)]
150    pub fn closures_min(&self) -> f64 {
151        self.closures_min as f64
152    }
153    /// Counts the number of function definitions in a scope
154    #[inline(always)]
155    pub fn functions_max(&self) -> f64 {
156        // Only function definitions are considered, not general declarations
157        self.functions_max as f64
158    }
159
160    /// Counts the number of closures in a scope
161    #[inline(always)]
162    pub fn closures_max(&self) -> f64 {
163        self.closures_max as f64
164    }
165    /// Returns the total number of function definitions and
166    /// closures in a scope
167    #[inline(always)]
168    pub fn total(&self) -> f64 {
169        self.functions_sum() + self.closures_sum()
170    }
171    #[inline(always)]
172    pub(crate) fn compute_sum(&mut self) {
173        self.functions_sum += self.functions;
174        self.closures_sum += self.closures;
175    }
176    #[inline(always)]
177    pub(crate) fn compute_minmax(&mut self) {
178        self.functions_min = self.functions_min.min(self.functions);
179        self.functions_max = self.functions_max.max(self.functions);
180        self.closures_min = self.closures_min.min(self.closures);
181        self.closures_max = self.closures_max.max(self.closures);
182        self.compute_sum();
183    }
184}
185
186#[doc(hidden)]
187pub trait Nom
188where
189    Self: Checker,
190{
191    fn compute(node: &Node, stats: &mut Stats) {
192        if Self::is_func(node) {
193            stats.functions += 1;
194            return;
195        }
196        if Self::is_closure(node) {
197            stats.closures += 1;
198        }
199    }
200}
201
202impl Nom for PythonCode {}
203impl Nom for MozjsCode {}
204impl Nom for JavascriptCode {}
205impl Nom for TypescriptCode {}
206impl Nom for TsxCode {}
207impl Nom for RustCode {}
208impl Nom for CppCode {}
209impl Nom for PreprocCode {}
210impl Nom for CcommentCode {}
211impl Nom for JavaCode {}
212
213#[cfg(test)]
214mod tests {
215    use std::path::PathBuf;
216
217    use super::*;
218
219    #[test]
220    fn python_nom() {
221        check_metrics!(
222            "def a():
223                 pass
224             def b():
225                 pass
226             def c():
227                 pass
228             x = lambda a : a + 42",
229            "foo.py",
230            PythonParser,
231            nom,
232            [
233                (functions_sum, 3, usize),
234                (closures_sum, 1, usize),
235                (total, 4, usize),
236                (functions_max, 1, usize),
237                (functions_min, 0, usize),
238                (closures_min, 0, usize),
239                (closures_max, 1, usize),
240            ],
241            [
242                (functions_average, 0.75), // number of spaces = 4
243                (closures_average, 0.25),
244                (average, 1.0)
245            ]
246        );
247    }
248
249    #[test]
250    fn rust_nom() {
251        check_metrics!(
252            "mod A { fn foo() {}}
253             mod B { fn foo() {}}
254             let closure = |i: i32| -> i32 { i + 42 };",
255            "foo.rs",
256            RustParser,
257            nom,
258            [
259                (functions_sum, 2, usize),
260                (closures_sum, 1, usize),
261                (total, 3, usize),
262                (functions_max, 1, usize),
263                (functions_min, 0, usize),
264                (closures_min, 0, usize),
265                (closures_max, 1, usize),
266            ],
267            [
268                (functions_average, 0.5), // number of spaces = 4
269                (closures_average, 0.25),
270                (average, 0.75)
271            ]
272        );
273    }
274
275    #[test]
276    fn c_nom() {
277        check_metrics!(
278            "int foo();
279
280             int foo() {
281                 return 0;
282             }",
283            "foo.c",
284            CppParser,
285            nom,
286            [
287                (functions_sum, 1, usize),
288                (closures_sum, 0, usize),
289                (total, 1, usize),
290                (functions_max, 1, usize),
291                (functions_min, 0, usize),
292                (closures_min, 0, usize),
293                (closures_max, 0, usize),
294            ],
295            [
296                (functions_average, 0.5), // number of spaces = 2
297                (closures_average, 0.0),
298                (average, 0.5)
299            ]
300        );
301    }
302
303    #[test]
304    fn cpp_nom() {
305        check_metrics!(
306            "struct A {
307                 void foo(int) {}
308                 void foo(double) {}
309             };
310             int b = [](int x) -> int { return x + 42; };",
311            "foo.cpp",
312            CppParser,
313            nom,
314            [
315                (functions_sum, 2, usize),
316                (closures_sum, 1, usize),
317                (total, 3, usize),
318                (functions_max, 1, usize),
319                (functions_min, 0, usize),
320                (closures_min, 0, usize),
321                (closures_max, 1, usize),
322            ],
323            [
324                (functions_average, 0.5), // number of spaces = 4
325                (closures_average, 0.25),
326                (average, 0.75)
327            ]
328        );
329    }
330
331    #[test]
332    fn javascript_nom() {
333        check_metrics!(
334            "function f(a, b) {
335                 function foo(a) {
336                     return a;
337                 }
338                 var bar = (function () {
339                     var counter = 0;
340                     return function () {
341                         counter += 1;
342                         return counter
343                     }
344                 })();
345                 return bar(foo(a), a);
346             }",
347            "foo.js",
348            JavascriptParser,
349            nom,
350            [
351                (functions_sum, 3, usize), // f, foo, bar
352                (closures_sum, 1, usize),  // return function ()
353                (total, 4, usize),
354                (functions_max, 1, usize),
355                (functions_min, 0, usize),
356                (closures_min, 0, usize),
357                (closures_max, 1, usize),
358            ],
359            [
360                (functions_average, 0.6), // number of spaces = 5
361                (closures_average, 0.2),
362                (average, 0.8)
363            ]
364        );
365    }
366
367    #[test]
368    fn javascript_call_nom() {
369        check_metrics!(
370            "add_task(async function test_safe_mode() {
371                 gAppInfo.inSafeMode = true;
372             });",
373            "foo.js",
374            JavascriptParser,
375            nom,
376            [
377                (functions_sum, 1, usize), // test_safe_mode
378                (closures_sum, 0, usize),
379                (total, 1, usize),
380                (functions_max, 1, usize),
381                (functions_min, 0, usize),
382                (closures_min, 0, usize),
383                (closures_max, 0, usize),
384            ],
385            [
386                (functions_average, 0.5), // number of spaces = 2
387                (closures_average, 0.0),
388                (average, 0.5)
389            ]
390        );
391    }
392
393    #[test]
394    fn javascript_assignment_nom() {
395        check_metrics!(
396            "AnimationTest.prototype.enableDisplay = function(element) {};",
397            "foo.js",
398            JavascriptParser,
399            nom,
400            [
401                (functions_sum, 1, usize),
402                (closures_sum, 0, usize),
403                (total, 1, usize),
404                (functions_max, 1, usize),
405                (functions_min, 0, usize),
406                (closures_min, 0, usize),
407                (closures_max, 0, usize),
408            ],
409            [
410                (functions_average, 0.5), // number of spaces = 2
411                (closures_average, 0.0),
412                (average, 0.5)
413            ]
414        );
415    }
416
417    #[test]
418    fn javascript_labeled_nom() {
419        check_metrics!(
420            "toJSON: function() {
421                 return this.inspect(true);
422             }",
423            "foo.js",
424            JavascriptParser,
425            nom,
426            [
427                (functions_sum, 1, usize),
428                (closures_sum, 0, usize),
429                (total, 1, usize),
430                (functions_max, 1, usize),
431                (functions_min, 0, usize),
432                (closures_min, 0, usize),
433                (closures_max, 0, usize),
434            ],
435            [
436                (functions_average, 0.5), // number of spaces = 2
437                (closures_average, 0.0),
438                (average, 0.5)
439            ]
440        );
441    }
442
443    #[test]
444    fn javascript_labeled_arrow_nom() {
445        check_metrics!(
446            "const dimConverters = {
447                pt: x => x,
448             };",
449            "foo.js",
450            JavascriptParser,
451            nom,
452            [
453                (functions_sum, 1, usize),
454                (closures_sum, 0, usize),
455                (total, 1, usize),
456                (functions_max, 1, usize),
457                (functions_min, 0, usize),
458                (closures_min, 0, usize),
459                (closures_max, 0, usize),
460            ],
461            [
462                (functions_average, 0.5), // number of spaces = 2
463                (closures_average, 0.0),
464                (average, 0.5)
465            ]
466        );
467    }
468
469    #[test]
470    fn javascript_pair_nom() {
471        check_metrics!(
472            "return {
473                 initialize: function(object) {
474                     this._object = object.toObject();
475                 },
476             }",
477            "foo.js",
478            JavascriptParser,
479            nom,
480            [
481                (functions_sum, 1, usize),
482                (closures_sum, 0, usize),
483                (total, 1, usize),
484                (functions_max, 1, usize),
485                (functions_min, 0, usize),
486                (closures_min, 0, usize),
487                (closures_max, 0, usize),
488            ],
489            [
490                (functions_average, 0.5), // number of spaces = 2
491                (closures_average, 0.0),
492                (average, 0.5)
493            ]
494        );
495    }
496
497    #[test]
498    fn javascript_unnamed_nom() {
499        check_metrics!(
500            "Ajax.getTransport = Try.these(
501                 function() {
502                     return function(){ return new XMLHttpRequest()}
503                 }
504             );",
505            "foo.js",
506            JavascriptParser,
507            nom,
508            [
509                (functions_sum, 0, usize),
510                (closures_sum, 2, usize),
511                (total, 2, usize),
512                (functions_max, 0, usize),
513                (functions_min, 0, usize),
514                (closures_min, 0, usize),
515                (closures_max, 1, usize),
516            ],
517            [
518                (functions_average, 0.0), // number of spaces = 3
519                (closures_average, 0.6666666666666666),
520                (average, 0.6666666666666666)
521            ]
522        );
523    }
524
525    #[test]
526    fn javascript_arrow_nom() {
527        check_metrics!(
528            "var materials = [\"Hydrogen\"];
529             materials.map(material => material.length);
530             let add = (a, b)  => a + b;",
531            "foo.js",
532            JavascriptParser,
533            nom,
534            [
535                (functions_sum, 1, usize), // add
536                (closures_sum, 1, usize),  // materials.map
537                (total, 2, usize),
538                (functions_max, 1, usize),
539                (functions_min, 0, usize),
540                (closures_min, 0, usize),
541                (closures_max, 1, usize),
542            ],
543            [
544                (functions_average, 0.3333333333333333), // number of spaces = 3
545                (closures_average, 0.3333333333333333),
546                (average, 0.6666666666666666)
547            ]
548        );
549    }
550
551    #[test]
552    fn javascript_arrow_assignment_nom() {
553        check_metrics!(
554            "sink.onPull = () => { };",
555            "foo.js",
556            JavascriptParser,
557            nom,
558            [
559                (functions_sum, 1, usize),
560                (closures_sum, 0, usize),
561                (total, 1, usize),
562                (functions_max, 1, usize),
563                (functions_min, 0, usize),
564                (closures_min, 0, usize),
565                (closures_max, 0, usize),
566            ],
567            [
568                (functions_average, 0.5), // number of spaces = 2
569                (closures_average, 0.0),
570                (average, 0.5)
571            ]
572        );
573    }
574
575    #[test]
576    fn javascript_arrow_new_nom() {
577        check_metrics!(
578            "const response = new Promise(resolve => channel.port1.onmessage = resolve);",
579            "foo.js",
580            JavascriptParser,
581            nom,
582            [
583                (functions_sum, 0, usize),
584                (closures_sum, 1, usize),
585                (total, 1, usize),
586                (functions_max, 0, usize),
587                (functions_min, 0, usize),
588                (closures_min, 0, usize),
589                (closures_max, 1, usize),
590            ],
591            [
592                (functions_average, 0.0), // number of spaces = 2
593                (closures_average, 0.5),
594                (average, 0.5)
595            ]
596        );
597    }
598
599    #[test]
600    fn javascript_arrow_call_nom() {
601        check_metrics!(
602            "let notDisabled = TestUtils.waitForCondition(
603                 () => !backbutton.hasAttribute(\"disabled\")
604             );",
605            "foo.js",
606            JavascriptParser,
607            nom,
608            [
609                (functions_sum, 0, usize),
610                (closures_sum, 1, usize),
611                (total, 1, usize),
612                (functions_max, 0, usize),
613                (functions_min, 0, usize),
614                (closures_min, 0, usize),
615                (closures_max, 1, usize),
616            ],
617            [
618                (functions_average, 0.0), // number of spaces = 2
619                (closures_average, 0.5),
620                (average, 0.5)
621            ]
622        );
623    }
624
625    #[test]
626    fn java_nom() {
627        check_metrics!(
628            "class A {
629                public void foo(){
630                    return;
631                }
632                public void bar(){
633                    return;
634                }  
635            }",
636            "foo.java",
637            JavaParser,
638            nom,
639            [
640                (functions_sum, 2, usize),
641                (closures_sum, 0, usize),
642                (total, 2, usize),
643                (functions_max, 1, usize),
644                (functions_min, 0, usize),
645                (closures_max, 0, usize),
646                (closures_min, 0, usize),
647            ],
648            [
649                (functions_average, 0.5), // number of spaces = 4
650                (closures_average, 0.0),
651                (average, 0.5)
652            ]
653        );
654    }
655
656    #[test]
657    fn java_closure_nom() {
658        check_metrics!(
659            "interface printable{  
660                void print();  
661              }
662              
663              interface IntFunc {
664                int func(int n);
665              }
666              
667              class Printer implements printable{  
668                public void print(){System.out.println(\"Hello\");}
669                  
670                public static void main(String args[]){  
671                  Printer  obj = new Printer();  
672                  obj.print();
673                  IntFunc meaning = (i) -> i + 42;
674                  int i = meaning.func(1);
675                }
676              }",
677            "foo.java",
678            JavaParser,
679            nom,
680            [
681                (functions_sum, 4, usize),
682                (closures_sum, 1, usize),
683                (total, 5, usize),
684                (functions_max, 1, usize),
685                (functions_min, 0, usize),
686                (closures_max, 1, usize),
687                (closures_min, 0, usize),
688            ],
689            [
690                (functions_average, 0.5), // number of spaces = 8
691                (closures_average, 0.125),
692                (average, 0.625)
693            ]
694        );
695    }
696}