Skip to main content

rust_code_analysis_code_split/metrics/
nom.rs

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