Skip to main content

codelore_rca/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    JavascriptCode,
208    TypescriptCode,
209    TsxCode,
210    CppCode,
211    RustCode,
212    PreprocCode,
213    CcommentCode,
214    JavaCode,
215    KotlinCode
216);
217
218#[cfg(test)]
219mod tests {
220    use crate::tools::check_metrics;
221
222    use super::*;
223
224    #[test]
225    fn python_nom() {
226        check_metrics::<PythonParser>(
227            "def a():
228                 pass
229             def b():
230                 pass
231             def c():
232                 pass
233             x = lambda a : a + 42",
234            "foo.py",
235            |metric| {
236                // Number of spaces = 4
237                insta::assert_json_snapshot!(
238                    metric.nom,
239                    @r###"
240                    {
241                      "functions": 3.0,
242                      "closures": 1.0,
243                      "functions_average": 0.75,
244                      "closures_average": 0.25,
245                      "total": 4.0,
246                      "average": 1.0,
247                      "functions_min": 0.0,
248                      "functions_max": 1.0,
249                      "closures_min": 0.0,
250                      "closures_max": 1.0
251                    }"###
252                );
253            },
254        );
255    }
256
257    #[test]
258    fn rust_nom() {
259        check_metrics::<RustParser>(
260            "mod A { fn foo() {}}
261             mod B { fn foo() {}}
262             let closure = |i: i32| -> i32 { i + 42 };",
263            "foo.rs",
264            |metric| {
265                // Number of spaces = 4
266                insta::assert_json_snapshot!(
267                    metric.nom,
268                    @r###"
269                    {
270                      "functions": 2.0,
271                      "closures": 1.0,
272                      "functions_average": 0.5,
273                      "closures_average": 0.25,
274                      "total": 3.0,
275                      "average": 0.75,
276                      "functions_min": 0.0,
277                      "functions_max": 1.0,
278                      "closures_min": 0.0,
279                      "closures_max": 1.0
280                    }"###
281                );
282            },
283        );
284    }
285
286    #[test]
287    fn c_nom() {
288        check_metrics::<CppParser>(
289            "int foo();
290
291             int foo() {
292                 return 0;
293             }",
294            "foo.c",
295            |metric| {
296                // Number of spaces = 2
297                insta::assert_json_snapshot!(
298                    metric.nom,
299                    @r###"
300                    {
301                      "functions": 1.0,
302                      "closures": 0.0,
303                      "functions_average": 0.5,
304                      "closures_average": 0.0,
305                      "total": 1.0,
306                      "average": 0.5,
307                      "functions_min": 0.0,
308                      "functions_max": 1.0,
309                      "closures_min": 0.0,
310                      "closures_max": 0.0
311                    }"###
312                );
313            },
314        );
315    }
316
317    #[test]
318    fn cpp_nom() {
319        check_metrics::<CppParser>(
320            "struct A {
321                 void foo(int) {}
322                 void foo(double) {}
323             };
324             int b = [](int x) -> int { return x + 42; };",
325            "foo.cpp",
326            |metric| {
327                // Number of spaces = 4
328                insta::assert_json_snapshot!(
329                    metric.nom,
330                    @r###"
331                    {
332                      "functions": 2.0,
333                      "closures": 1.0,
334                      "functions_average": 0.5,
335                      "closures_average": 0.25,
336                      "total": 3.0,
337                      "average": 0.75,
338                      "functions_min": 0.0,
339                      "functions_max": 1.0,
340                      "closures_min": 0.0,
341                      "closures_max": 1.0
342                    }"###
343                );
344            },
345        );
346    }
347
348    #[test]
349    fn javascript_nom() {
350        check_metrics::<JavascriptParser>(
351            "function f(a, b) {
352                 function foo(a) {
353                     return a;
354                 }
355                 var bar = (function () {
356                     var counter = 0;
357                     return function () {
358                         counter += 1;
359                         return counter
360                     }
361                 })();
362                 return bar(foo(a), a);
363             }",
364            "foo.js",
365            |metric| {
366                // Number of spaces = 5
367                // functions: f, foo, bar
368                // closures:  return function ()
369                insta::assert_json_snapshot!(
370                    metric.nom,
371                    @r###"
372                    {
373                      "functions": 3.0,
374                      "closures": 1.0,
375                      "functions_average": 0.6,
376                      "closures_average": 0.2,
377                      "total": 4.0,
378                      "average": 0.8,
379                      "functions_min": 0.0,
380                      "functions_max": 1.0,
381                      "closures_min": 0.0,
382                      "closures_max": 1.0
383                    }"###
384                );
385            },
386        );
387    }
388
389    #[test]
390    fn javascript_call_nom() {
391        check_metrics::<JavascriptParser>(
392            "add_task(async function test_safe_mode() {
393                 gAppInfo.inSafeMode = true;
394             });",
395            "foo.js",
396            |metric| {
397                // Number of spaces = 2
398                // functions: test_safe_mode
399                insta::assert_json_snapshot!(
400                    metric.nom,
401                    @r###"
402                    {
403                      "functions": 1.0,
404                      "closures": 0.0,
405                      "functions_average": 0.5,
406                      "closures_average": 0.0,
407                      "total": 1.0,
408                      "average": 0.5,
409                      "functions_min": 0.0,
410                      "functions_max": 1.0,
411                      "closures_min": 0.0,
412                      "closures_max": 0.0
413                    }"###
414                );
415            },
416        );
417    }
418
419    #[test]
420    fn javascript_assignment_nom() {
421        check_metrics::<JavascriptParser>(
422            "AnimationTest.prototype.enableDisplay = function(element) {};",
423            "foo.js",
424            |metric| {
425                // Number of spaces = 2
426                insta::assert_json_snapshot!(
427                    metric.nom,
428                    @r###"
429                    {
430                      "functions": 1.0,
431                      "closures": 0.0,
432                      "functions_average": 0.5,
433                      "closures_average": 0.0,
434                      "total": 1.0,
435                      "average": 0.5,
436                      "functions_min": 0.0,
437                      "functions_max": 1.0,
438                      "closures_min": 0.0,
439                      "closures_max": 0.0
440                    }"###
441                );
442            },
443        );
444    }
445
446    #[test]
447    fn javascript_labeled_nom() {
448        check_metrics::<JavascriptParser>(
449            "toJSON: function() {
450                 return this.inspect(true);
451             }",
452            "foo.js",
453            |metric| {
454                // Number of spaces = 2
455                insta::assert_json_snapshot!(
456                    metric.nom,
457                    @r###"
458                    {
459                      "functions": 1.0,
460                      "closures": 0.0,
461                      "functions_average": 0.5,
462                      "closures_average": 0.0,
463                      "total": 1.0,
464                      "average": 0.5,
465                      "functions_min": 0.0,
466                      "functions_max": 1.0,
467                      "closures_min": 0.0,
468                      "closures_max": 0.0
469                    }"###
470                );
471            },
472        );
473    }
474
475    #[test]
476    fn javascript_labeled_arrow_nom() {
477        check_metrics::<JavascriptParser>(
478            "const dimConverters = {
479                pt: x => x,
480             };",
481            "foo.js",
482            |metric| {
483                // Number of spaces = 2
484                insta::assert_json_snapshot!(
485                    metric.nom,
486                    @r###"
487                    {
488                      "functions": 1.0,
489                      "closures": 0.0,
490                      "functions_average": 0.5,
491                      "closures_average": 0.0,
492                      "total": 1.0,
493                      "average": 0.5,
494                      "functions_min": 0.0,
495                      "functions_max": 1.0,
496                      "closures_min": 0.0,
497                      "closures_max": 0.0
498                    }"###
499                );
500            },
501        );
502    }
503
504    #[test]
505    fn javascript_pair_nom() {
506        check_metrics::<JavascriptParser>(
507            "return {
508                 initialize: function(object) {
509                     this._object = object.toObject();
510                 },
511             }",
512            "foo.js",
513            |metric| {
514                // Number of spaces = 2
515                insta::assert_json_snapshot!(
516                    metric.nom,
517                    @r###"
518                    {
519                      "functions": 1.0,
520                      "closures": 0.0,
521                      "functions_average": 0.5,
522                      "closures_average": 0.0,
523                      "total": 1.0,
524                      "average": 0.5,
525                      "functions_min": 0.0,
526                      "functions_max": 1.0,
527                      "closures_min": 0.0,
528                      "closures_max": 0.0
529                    }"###
530                );
531            },
532        );
533    }
534
535    #[test]
536    fn javascript_unnamed_nom() {
537        check_metrics::<JavascriptParser>(
538            "Ajax.getTransport = Try.these(
539                 function() {
540                     return function(){ return new XMLHttpRequest()}
541                 }
542             );",
543            "foo.js",
544            |metric| {
545                // Number of spaces = 3
546                insta::assert_json_snapshot!(
547                    metric.nom,
548                    @r###"
549                    {
550                      "functions": 0.0,
551                      "closures": 2.0,
552                      "functions_average": 0.0,
553                      "closures_average": 0.6666666666666666,
554                      "total": 2.0,
555                      "average": 0.6666666666666666,
556                      "functions_min": 0.0,
557                      "functions_max": 0.0,
558                      "closures_min": 0.0,
559                      "closures_max": 1.0
560                    }"###
561                );
562            },
563        );
564    }
565
566    #[test]
567    fn javascript_arrow_nom() {
568        check_metrics::<JavascriptParser>(
569            "var materials = [\"Hydrogen\"];
570             materials.map(material => material.length);
571             let add = (a, b)  => a + b;",
572            "foo.js",
573            |metric| {
574                // Number of spaces = 3
575                // Functions: add
576                // Closures: material.map
577                insta::assert_json_snapshot!(
578                    metric.nom,
579                    @r###"
580                    {
581                      "functions": 1.0,
582                      "closures": 1.0,
583                      "functions_average": 0.3333333333333333,
584                      "closures_average": 0.3333333333333333,
585                      "total": 2.0,
586                      "average": 0.6666666666666666,
587                      "functions_min": 0.0,
588                      "functions_max": 1.0,
589                      "closures_min": 0.0,
590                      "closures_max": 1.0
591                    }"###
592                );
593            },
594        );
595    }
596
597    #[test]
598    fn javascript_arrow_assignment_nom() {
599        check_metrics::<JavascriptParser>("sink.onPull = () => { };", "foo.js", |metric| {
600            // Number of spaces = 2
601            insta::assert_json_snapshot!(
602                metric.nom,
603                @r###"
604                    {
605                      "functions": 1.0,
606                      "closures": 0.0,
607                      "functions_average": 0.5,
608                      "closures_average": 0.0,
609                      "total": 1.0,
610                      "average": 0.5,
611                      "functions_min": 0.0,
612                      "functions_max": 1.0,
613                      "closures_min": 0.0,
614                      "closures_max": 0.0
615                    }"###
616            );
617        });
618    }
619
620    #[test]
621    fn javascript_arrow_new_nom() {
622        check_metrics::<JavascriptParser>(
623            "const response = new Promise(resolve => channel.port1.onmessage = resolve);",
624            "foo.js",
625            |metric| {
626                // Number of spaces = 2
627                insta::assert_json_snapshot!(
628                    metric.nom,
629                    @r###"
630                    {
631                      "functions": 0.0,
632                      "closures": 1.0,
633                      "functions_average": 0.0,
634                      "closures_average": 0.5,
635                      "total": 1.0,
636                      "average": 0.5,
637                      "functions_min": 0.0,
638                      "functions_max": 0.0,
639                      "closures_min": 0.0,
640                      "closures_max": 1.0
641                    }"###
642                );
643            },
644        );
645    }
646
647    #[test]
648    fn javascript_arrow_call_nom() {
649        check_metrics::<JavascriptParser>(
650            "let notDisabled = TestUtils.waitForCondition(
651                 () => !backbutton.hasAttribute(\"disabled\")
652             );",
653            "foo.js",
654            |metric| {
655                // Number of spaces = 2
656                insta::assert_json_snapshot!(
657                    metric.nom,
658                    @r###"
659                    {
660                      "functions": 0.0,
661                      "closures": 1.0,
662                      "functions_average": 0.0,
663                      "closures_average": 0.5,
664                      "total": 1.0,
665                      "average": 0.5,
666                      "functions_min": 0.0,
667                      "functions_max": 0.0,
668                      "closures_min": 0.0,
669                      "closures_max": 1.0
670                    }"###
671                );
672            },
673        );
674    }
675
676    #[test]
677    fn java_nom() {
678        check_metrics::<JavaParser>(
679            "class A {
680                public void foo(){
681                    return;
682                }
683                public void bar(){
684                    return;
685                }
686            }",
687            "foo.java",
688            |metric| {
689                // Number of spaces = 4
690                insta::assert_json_snapshot!(
691                    metric.nom,
692                    @r###"
693                    {
694                      "functions": 2.0,
695                      "closures": 0.0,
696                      "functions_average": 0.5,
697                      "closures_average": 0.0,
698                      "total": 2.0,
699                      "average": 0.5,
700                      "functions_min": 0.0,
701                      "functions_max": 1.0,
702                      "closures_min": 0.0,
703                      "closures_max": 0.0
704                    }"###
705                );
706            },
707        );
708    }
709
710    #[test]
711    fn java_closure_nom() {
712        check_metrics::<JavaParser>(
713            "interface printable{
714                void print();
715              }
716
717              interface IntFunc {
718                int func(int n);
719              }
720
721              class Printer implements printable{
722                public void print(){System.out.println(\"Hello\");}
723
724                public static void main(String args[]){
725                  Printer  obj = new Printer();
726                  obj.print();
727                  IntFunc meaning = (i) -> i + 42;
728                  int i = meaning.func(1);
729                }
730              }",
731            "foo.java",
732            |metric| {
733                // Number of spaces = 8
734                insta::assert_json_snapshot!(
735                    metric.nom,
736                    @r###"
737                    {
738                      "functions": 4.0,
739                      "closures": 1.0,
740                      "functions_average": 0.5,
741                      "closures_average": 0.125,
742                      "total": 5.0,
743                      "average": 0.625,
744                      "functions_min": 0.0,
745                      "functions_max": 1.0,
746                      "closures_min": 0.0,
747                      "closures_max": 1.0
748                    }"###
749                );
750            },
751        );
752    }
753}