1#![allow(clippy::wildcard_imports, clippy::enum_glob_use)]
8#![allow(
15 clippy::cast_precision_loss,
16 clippy::cast_possible_truncation,
17 clippy::cast_sign_loss
18)]
19
20use serde::Serialize;
21use serde::ser::{SerializeStruct, Serializer};
22use std::fmt;
23
24use crate::checker::Checker;
25use crate::macros::implement_metric_trait;
26
27use crate::*;
28
29#[derive(Clone, Debug)]
31pub struct Stats {
32 functions: usize,
33 closures: usize,
34 functions_sum: usize,
35 closures_sum: usize,
36 functions_min: usize,
37 functions_max: usize,
38 closures_min: usize,
39 closures_max: usize,
40 space_count: usize,
41}
42
43impl Default for Stats {
44 fn default() -> Self {
45 Self {
46 functions: 0,
47 closures: 0,
48 functions_sum: 0,
49 closures_sum: 0,
50 functions_min: usize::MAX,
51 functions_max: 0,
52 closures_min: usize::MAX,
53 closures_max: 0,
54 space_count: 1,
55 }
56 }
57}
58
59impl Serialize for Stats {
60 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
61 where
62 S: Serializer,
63 {
64 let mut st = serializer.serialize_struct("nom", 10)?;
65 st.serialize_field("functions", &self.functions_sum())?;
66 st.serialize_field("closures", &self.closures_sum())?;
67 st.serialize_field("functions_average", &self.functions_average())?;
68 st.serialize_field("closures_average", &self.closures_average())?;
69 st.serialize_field("total", &self.total())?;
70 st.serialize_field("average", &self.average())?;
71 st.serialize_field("functions_min", &self.functions_min())?;
72 st.serialize_field("functions_max", &self.functions_max())?;
73 st.serialize_field("closures_min", &self.closures_min())?;
74 st.serialize_field("closures_max", &self.closures_max())?;
75 st.end()
76 }
77}
78
79impl fmt::Display for Stats {
80 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
81 write!(
82 f,
83 "functions: {}, \
84 closures: {}, \
85 functions_average: {}, \
86 closures_average: {}, \
87 total: {}, \
88 average: {}, \
89 functions_min: {}, \
90 functions_max: {}, \
91 closures_min: {}, \
92 closures_max: {}",
93 self.functions_sum(),
94 self.closures_sum(),
95 self.functions_average(),
96 self.closures_average(),
97 self.total(),
98 self.average(),
99 self.functions_min(),
100 self.functions_max(),
101 self.closures_min(),
102 self.closures_max(),
103 )
104 }
105}
106
107impl Stats {
108 pub fn merge(&mut self, other: &Stats) {
110 self.functions_min = self.functions_min.min(other.functions_min);
111 self.functions_max = self.functions_max.max(other.functions_max);
112 self.closures_min = self.closures_min.min(other.closures_min);
113 self.closures_max = self.closures_max.max(other.closures_max);
114 self.functions_sum += other.functions_sum;
115 self.closures_sum += other.closures_sum;
116 self.space_count += other.space_count;
117 }
118
119 #[inline]
121 #[must_use]
122 pub fn functions(&self) -> f64 {
123 self.functions as f64
125 }
126
127 #[inline]
129 #[must_use]
130 pub fn closures(&self) -> f64 {
131 self.closures as f64
132 }
133
134 #[inline]
136 #[must_use]
137 pub fn functions_sum(&self) -> f64 {
138 self.functions_sum as f64
140 }
141
142 #[inline]
144 #[must_use]
145 pub fn closures_sum(&self) -> f64 {
146 self.closures_sum as f64
147 }
148
149 #[inline]
151 #[must_use]
152 pub fn functions_average(&self) -> f64 {
153 self.functions_sum() / self.space_count as f64
154 }
155
156 #[inline]
158 #[must_use]
159 pub fn closures_average(&self) -> f64 {
160 self.closures_sum() / self.space_count as f64
161 }
162
163 #[inline]
165 #[must_use]
166 pub fn average(&self) -> f64 {
167 self.total() / self.space_count as f64
168 }
169
170 #[inline]
176 #[must_use]
177 pub fn functions_min(&self) -> f64 {
178 if self.functions_min == usize::MAX {
180 0.0
181 } else {
182 self.functions_min as f64
183 }
184 }
185
186 #[inline]
190 #[must_use]
191 pub fn closures_min(&self) -> f64 {
192 if self.closures_min == usize::MAX {
193 0.0
194 } else {
195 self.closures_min as f64
196 }
197 }
198 #[inline]
200 #[must_use]
201 pub fn functions_max(&self) -> f64 {
202 self.functions_max as f64
204 }
205
206 #[inline]
208 #[must_use]
209 pub fn closures_max(&self) -> f64 {
210 self.closures_max as f64
211 }
212 #[inline]
215 #[must_use]
216 pub fn total(&self) -> f64 {
217 self.functions_sum() + self.closures_sum()
218 }
219 #[inline]
220 pub(crate) fn compute_sum(&mut self) {
221 self.functions_sum += self.functions;
222 self.closures_sum += self.closures;
223 }
224 #[inline]
225 pub(crate) fn compute_minmax(&mut self) {
226 self.functions_min = self.functions_min.min(self.functions);
227 self.functions_max = self.functions_max.max(self.functions);
228 self.closures_min = self.closures_min.min(self.closures);
229 self.closures_max = self.closures_max.max(self.closures);
230 self.compute_sum();
231 }
232}
233
234#[doc(hidden)]
235pub trait Nom
237where
238 Self: Checker,
239{
240 fn compute(node: &Node, stats: &mut Stats) {
243 if Self::is_func(node) {
244 stats.functions += 1;
245 return;
246 }
247 if Self::is_closure(node) {
248 stats.closures += 1;
249 }
250 }
251}
252
253implement_metric_trait!(
254 [Nom],
255 PythonCode,
256 MozjsCode,
257 JavascriptCode,
258 TypescriptCode,
259 TsxCode,
260 CppCode,
261 RustCode,
262 PreprocCode,
263 CcommentCode,
264 JavaCode,
265 KotlinCode,
266 GoCode,
267 PerlCode,
268 BashCode,
269 LuaCode,
270 TclCode,
271 PhpCode,
272 CsharpCode,
273 ElixirCode,
274 RubyCode,
275 GroovyCode
276);
277
278#[cfg(test)]
279#[allow(
280 clippy::float_cmp,
281 clippy::cast_precision_loss,
282 clippy::cast_possible_truncation,
283 clippy::cast_sign_loss,
284 clippy::similar_names,
285 clippy::doc_markdown,
286 clippy::needless_raw_string_hashes,
287 clippy::too_many_lines
288)]
289mod tests {
290 use crate::tools::check_metrics;
291
292 use super::*;
293
294 #[test]
299 fn nom_empty_file_min_is_zero() {
300 let stats = Stats::default();
301 assert_eq!(stats.functions_min(), 0.0);
302 assert_eq!(stats.closures_min(), 0.0);
303 }
304
305 #[test]
306 fn python_nom() {
307 check_metrics::<PythonParser>(
308 "def a():
309 pass
310 def b():
311 pass
312 def c():
313 pass
314 x = lambda a : a + 42",
315 "foo.py",
316 |metric| {
317 insta::assert_json_snapshot!(
319 metric.nom,
320 @r###"
321 {
322 "functions": 3.0,
323 "closures": 1.0,
324 "functions_average": 0.75,
325 "closures_average": 0.25,
326 "total": 4.0,
327 "average": 1.0,
328 "functions_min": 0.0,
329 "functions_max": 1.0,
330 "closures_min": 0.0,
331 "closures_max": 1.0
332 }"###
333 );
334 },
335 );
336 }
337
338 #[test]
339 fn rust_nom() {
340 check_metrics::<RustParser>(
341 "mod A { fn foo() {}}
342 mod B { fn foo() {}}
343 let closure = |i: i32| -> i32 { i + 42 };",
344 "foo.rs",
345 |metric| {
346 insta::assert_json_snapshot!(
348 metric.nom,
349 @r###"
350 {
351 "functions": 2.0,
352 "closures": 1.0,
353 "functions_average": 0.5,
354 "closures_average": 0.25,
355 "total": 3.0,
356 "average": 0.75,
357 "functions_min": 0.0,
358 "functions_max": 1.0,
359 "closures_min": 0.0,
360 "closures_max": 1.0
361 }"###
362 );
363 },
364 );
365 }
366
367 #[test]
368 fn c_nom() {
369 check_metrics::<CppParser>(
370 "int foo();
371
372 int foo() {
373 return 0;
374 }",
375 "foo.c",
376 |metric| {
377 insta::assert_json_snapshot!(
379 metric.nom,
380 @r###"
381 {
382 "functions": 1.0,
383 "closures": 0.0,
384 "functions_average": 0.5,
385 "closures_average": 0.0,
386 "total": 1.0,
387 "average": 0.5,
388 "functions_min": 0.0,
389 "functions_max": 1.0,
390 "closures_min": 0.0,
391 "closures_max": 0.0
392 }"###
393 );
394 },
395 );
396 }
397
398 #[test]
399 fn cpp_nom() {
400 check_metrics::<CppParser>(
401 "struct A {
402 void foo(int) {}
403 void foo(double) {}
404 };
405 int b = [](int x) -> int { return x + 42; };",
406 "foo.cpp",
407 |metric| {
408 insta::assert_json_snapshot!(
410 metric.nom,
411 @r###"
412 {
413 "functions": 2.0,
414 "closures": 1.0,
415 "functions_average": 0.5,
416 "closures_average": 0.25,
417 "total": 3.0,
418 "average": 0.75,
419 "functions_min": 0.0,
420 "functions_max": 1.0,
421 "closures_min": 0.0,
422 "closures_max": 1.0
423 }"###
424 );
425 },
426 );
427 }
428
429 #[test]
434 fn cpp_free_and_member_functions() {
435 check_metrics::<CppParser>(
436 "int free_fn(int x) { return x; }
437 struct S {
438 int member_fn(int x) { return x + 1; }
439 };",
440 "foo.cpp",
441 |metric| {
442 let s = &metric.nom;
444 assert_eq!(s.functions_sum(), 2.0);
445 assert_eq!(s.closures_sum(), 0.0);
446 assert_eq!(s.total(), 2.0);
447 insta::assert_json_snapshot!(metric.nom);
448 },
449 );
450 }
451
452 #[test]
456 fn cpp_static_member_function() {
457 check_metrics::<CppParser>(
458 "struct S {
459 static int factory(int x) { return x; }
460 int method(int x) { return x + 1; }
461 };",
462 "foo.cpp",
463 |metric| {
464 let s = &metric.nom;
465 assert_eq!(s.functions_sum(), 2.0);
466 assert_eq!(s.closures_sum(), 0.0);
467 insta::assert_json_snapshot!(metric.nom);
468 },
469 );
470 }
471
472 #[test]
477 fn cpp_constructor_and_destructor() {
478 check_metrics::<CppParser>(
479 "struct S {
480 S() {}
481 ~S() {}
482 int method() { return 0; }
483 };",
484 "foo.cpp",
485 |metric| {
486 let s = &metric.nom;
487 assert_eq!(s.functions_sum(), 3.0);
489 assert_eq!(s.closures_sum(), 0.0);
490 insta::assert_json_snapshot!(metric.nom);
491 },
492 );
493 }
494
495 #[test]
499 fn cpp_operator_overloads() {
500 check_metrics::<CppParser>(
501 "struct V {
502 int x;
503 V operator+(const V& o) const { return V{x + o.x}; }
504 bool operator==(const V& o) const { return x == o.x; }
505 };",
506 "foo.cpp",
507 |metric| {
508 let s = &metric.nom;
509 assert_eq!(s.functions_sum(), 2.0);
510 assert_eq!(s.closures_sum(), 0.0);
511 insta::assert_json_snapshot!(metric.nom);
512 },
513 );
514 }
515
516 #[test]
520 fn cpp_function_template() {
521 check_metrics::<CppParser>(
522 "template<typename T>
523 T identity(T x) { return x; }",
524 "foo.cpp",
525 |metric| {
526 let s = &metric.nom;
527 assert_eq!(s.functions_sum(), 1.0);
528 assert_eq!(s.closures_sum(), 0.0);
529 insta::assert_json_snapshot!(metric.nom);
530 },
531 );
532 }
533
534 #[test]
538 fn cpp_class_template_members() {
539 check_metrics::<CppParser>(
540 "template<typename T>
541 struct Box {
542 T value;
543 T get() const { return value; }
544 void set(T v) { value = v; }
545 };",
546 "foo.cpp",
547 |metric| {
548 let s = &metric.nom;
549 assert_eq!(s.functions_sum(), 2.0);
550 assert_eq!(s.closures_sum(), 0.0);
551 insta::assert_json_snapshot!(metric.nom);
552 },
553 );
554 }
555
556 #[test]
561 fn cpp_lambdas_inside_function() {
562 check_metrics::<CppParser>(
563 "int run() {
564 auto add = [](int a, int b) { return a + b; };
565 auto mul = [](int a, int b) { return a * b; };
566 return add(1, 2) + mul(3, 4);
567 }",
568 "foo.cpp",
569 |metric| {
570 let s = &metric.nom;
571 assert_eq!(s.functions_sum(), 1.0);
573 assert_eq!(s.closures_sum(), 2.0);
574 assert_eq!(s.total(), 3.0);
575 insta::assert_json_snapshot!(metric.nom);
576 },
577 );
578 }
579
580 #[test]
581 fn javascript_nom() {
582 check_metrics::<JavascriptParser>(
583 "function f(a, b) {
584 function foo(a) {
585 return a;
586 }
587 var bar = (function () {
588 var counter = 0;
589 return function () {
590 counter += 1;
591 return counter
592 }
593 })();
594 return bar(foo(a), a);
595 }",
596 "foo.js",
597 |metric| {
598 insta::assert_json_snapshot!(
602 metric.nom,
603 @r###"
604 {
605 "functions": 3.0,
606 "closures": 1.0,
607 "functions_average": 0.6,
608 "closures_average": 0.2,
609 "total": 4.0,
610 "average": 0.8,
611 "functions_min": 0.0,
612 "functions_max": 1.0,
613 "closures_min": 0.0,
614 "closures_max": 1.0
615 }"###
616 );
617 },
618 );
619 }
620
621 #[test]
622 fn javascript_call_nom() {
623 check_metrics::<JavascriptParser>(
624 "add_task(async function test_safe_mode() {
625 gAppInfo.inSafeMode = true;
626 });",
627 "foo.js",
628 |metric| {
629 insta::assert_json_snapshot!(
632 metric.nom,
633 @r###"
634 {
635 "functions": 1.0,
636 "closures": 0.0,
637 "functions_average": 0.5,
638 "closures_average": 0.0,
639 "total": 1.0,
640 "average": 0.5,
641 "functions_min": 0.0,
642 "functions_max": 1.0,
643 "closures_min": 0.0,
644 "closures_max": 0.0
645 }"###
646 );
647 },
648 );
649 }
650
651 #[test]
652 fn javascript_assignment_nom() {
653 check_metrics::<JavascriptParser>(
654 "AnimationTest.prototype.enableDisplay = function(element) {};",
655 "foo.js",
656 |metric| {
657 insta::assert_json_snapshot!(
659 metric.nom,
660 @r###"
661 {
662 "functions": 1.0,
663 "closures": 0.0,
664 "functions_average": 0.5,
665 "closures_average": 0.0,
666 "total": 1.0,
667 "average": 0.5,
668 "functions_min": 0.0,
669 "functions_max": 1.0,
670 "closures_min": 0.0,
671 "closures_max": 0.0
672 }"###
673 );
674 },
675 );
676 }
677
678 #[test]
679 fn javascript_labeled_nom() {
680 check_metrics::<JavascriptParser>(
681 "toJSON: function() {
682 return this.inspect(true);
683 }",
684 "foo.js",
685 |metric| {
686 insta::assert_json_snapshot!(
688 metric.nom,
689 @r###"
690 {
691 "functions": 1.0,
692 "closures": 0.0,
693 "functions_average": 0.5,
694 "closures_average": 0.0,
695 "total": 1.0,
696 "average": 0.5,
697 "functions_min": 0.0,
698 "functions_max": 1.0,
699 "closures_min": 0.0,
700 "closures_max": 0.0
701 }"###
702 );
703 },
704 );
705 }
706
707 #[test]
708 fn javascript_labeled_arrow_nom() {
709 check_metrics::<JavascriptParser>(
710 "const dimConverters = {
711 pt: x => x,
712 };",
713 "foo.js",
714 |metric| {
715 insta::assert_json_snapshot!(
717 metric.nom,
718 @r###"
719 {
720 "functions": 1.0,
721 "closures": 0.0,
722 "functions_average": 0.5,
723 "closures_average": 0.0,
724 "total": 1.0,
725 "average": 0.5,
726 "functions_min": 0.0,
727 "functions_max": 1.0,
728 "closures_min": 0.0,
729 "closures_max": 0.0
730 }"###
731 );
732 },
733 );
734 }
735
736 #[test]
737 fn javascript_pair_nom() {
738 check_metrics::<JavascriptParser>(
739 "return {
740 initialize: function(object) {
741 this._object = object.toObject();
742 },
743 }",
744 "foo.js",
745 |metric| {
746 insta::assert_json_snapshot!(
748 metric.nom,
749 @r###"
750 {
751 "functions": 1.0,
752 "closures": 0.0,
753 "functions_average": 0.5,
754 "closures_average": 0.0,
755 "total": 1.0,
756 "average": 0.5,
757 "functions_min": 0.0,
758 "functions_max": 1.0,
759 "closures_min": 0.0,
760 "closures_max": 0.0
761 }"###
762 );
763 },
764 );
765 }
766
767 fn check_returned_object_arrow_nom<T: ParserTrait>(file_name: &str) {
768 check_metrics::<T>(
769 "function f() { return { foo: x => x }; }",
770 file_name,
771 |metric| {
772 insta::allow_duplicates! {
773 insta::assert_json_snapshot!(
774 metric.nom,
775 @r###"
776 {
777 "functions": 2.0,
778 "closures": 0.0,
779 "functions_average": 0.6666666666666666,
780 "closures_average": 0.0,
781 "total": 2.0,
782 "average": 0.6666666666666666,
783 "functions_min": 0.0,
784 "functions_max": 1.0,
785 "closures_min": 0.0,
786 "closures_max": 0.0
787 }"###
788 );
789 }
790 },
791 );
792 }
793
794 #[test]
795 fn javascript_returned_object_arrow_nom() {
796 check_returned_object_arrow_nom::<JavascriptParser>("foo.js");
797 }
798
799 #[test]
800 fn mozjs_returned_object_arrow_nom() {
801 check_returned_object_arrow_nom::<MozjsParser>("foo.js");
802 }
803
804 #[test]
805 fn typescript_returned_object_arrow_nom() {
806 check_returned_object_arrow_nom::<TypescriptParser>("foo.ts");
807 }
808
809 #[test]
810 fn tsx_returned_object_arrow_nom() {
811 check_returned_object_arrow_nom::<TsxParser>("foo.tsx");
812 }
813
814 #[test]
815 fn javascript_unnamed_nom() {
816 check_metrics::<JavascriptParser>(
817 "Ajax.getTransport = Try.these(
818 function() {
819 return function(){ return new XMLHttpRequest()}
820 }
821 );",
822 "foo.js",
823 |metric| {
824 insta::assert_json_snapshot!(
826 metric.nom,
827 @r###"
828 {
829 "functions": 0.0,
830 "closures": 2.0,
831 "functions_average": 0.0,
832 "closures_average": 0.6666666666666666,
833 "total": 2.0,
834 "average": 0.6666666666666666,
835 "functions_min": 0.0,
836 "functions_max": 0.0,
837 "closures_min": 0.0,
838 "closures_max": 1.0
839 }"###
840 );
841 },
842 );
843 }
844
845 #[test]
846 fn javascript_arrow_nom() {
847 check_metrics::<JavascriptParser>(
848 "var materials = [\"Hydrogen\"];
849 materials.map(material => material.length);
850 let add = (a, b) => a + b;",
851 "foo.js",
852 |metric| {
853 insta::assert_json_snapshot!(
857 metric.nom,
858 @r###"
859 {
860 "functions": 1.0,
861 "closures": 1.0,
862 "functions_average": 0.3333333333333333,
863 "closures_average": 0.3333333333333333,
864 "total": 2.0,
865 "average": 0.6666666666666666,
866 "functions_min": 0.0,
867 "functions_max": 1.0,
868 "closures_min": 0.0,
869 "closures_max": 1.0
870 }"###
871 );
872 },
873 );
874 }
875
876 #[test]
877 fn javascript_arrow_assignment_nom() {
878 check_metrics::<JavascriptParser>("sink.onPull = () => { };", "foo.js", |metric| {
879 insta::assert_json_snapshot!(
881 metric.nom,
882 @r###"
883 {
884 "functions": 1.0,
885 "closures": 0.0,
886 "functions_average": 0.5,
887 "closures_average": 0.0,
888 "total": 1.0,
889 "average": 0.5,
890 "functions_min": 0.0,
891 "functions_max": 1.0,
892 "closures_min": 0.0,
893 "closures_max": 0.0
894 }"###
895 );
896 });
897 }
898
899 #[test]
900 fn javascript_arrow_new_nom() {
901 check_metrics::<JavascriptParser>(
902 "const response = new Promise(resolve => channel.port1.onmessage = resolve);",
903 "foo.js",
904 |metric| {
905 insta::assert_json_snapshot!(
907 metric.nom,
908 @r###"
909 {
910 "functions": 0.0,
911 "closures": 1.0,
912 "functions_average": 0.0,
913 "closures_average": 0.5,
914 "total": 1.0,
915 "average": 0.5,
916 "functions_min": 0.0,
917 "functions_max": 0.0,
918 "closures_min": 0.0,
919 "closures_max": 1.0
920 }"###
921 );
922 },
923 );
924 }
925
926 #[test]
927 fn javascript_arrow_call_nom() {
928 check_metrics::<JavascriptParser>(
929 "let notDisabled = TestUtils.waitForCondition(
930 () => !backbutton.hasAttribute(\"disabled\")
931 );",
932 "foo.js",
933 |metric| {
934 insta::assert_json_snapshot!(
936 metric.nom,
937 @r###"
938 {
939 "functions": 0.0,
940 "closures": 1.0,
941 "functions_average": 0.0,
942 "closures_average": 0.5,
943 "total": 1.0,
944 "average": 0.5,
945 "functions_min": 0.0,
946 "functions_max": 0.0,
947 "closures_min": 0.0,
948 "closures_max": 1.0
949 }"###
950 );
951 },
952 );
953 }
954
955 #[test]
956 fn java_nom() {
957 check_metrics::<JavaParser>(
958 "class A {
959 public void foo(){
960 return;
961 }
962 public void bar(){
963 return;
964 }
965 }",
966 "foo.java",
967 |metric| {
968 insta::assert_json_snapshot!(
970 metric.nom,
971 @r###"
972 {
973 "functions": 2.0,
974 "closures": 0.0,
975 "functions_average": 0.5,
976 "closures_average": 0.0,
977 "total": 2.0,
978 "average": 0.5,
979 "functions_min": 0.0,
980 "functions_max": 1.0,
981 "closures_min": 0.0,
982 "closures_max": 0.0
983 }"###
984 );
985 },
986 );
987 }
988
989 #[test]
990 fn csharp_nom() {
991 check_metrics::<CsharpParser>(
992 "class A {
993 public void Foo() {
994 return;
995 }
996 public void Bar() {
997 return;
998 }
999 public int X { get; set; }
1000 public void Outer() {
1001 void Local() { return; }
1002 Local();
1003 }
1004 }",
1005 "foo.cs",
1006 |metric| {
1007 insta::assert_json_snapshot!(
1012 metric.nom,
1013 @r###"
1014 {
1015 "functions": 6.0,
1016 "closures": 0.0,
1017 "functions_average": 0.75,
1018 "closures_average": 0.0,
1019 "total": 6.0,
1020 "average": 0.75,
1021 "functions_min": 0.0,
1022 "functions_max": 1.0,
1023 "closures_min": 0.0,
1024 "closures_max": 0.0
1025 }"###
1026 );
1027 },
1028 );
1029 }
1030
1031 #[test]
1032 fn csharp_closure_nom() {
1033 check_metrics::<CsharpParser>(
1034 "class A {
1035 public void Run() {
1036 System.Func<int, int> f = x => x + 1;
1037 System.Action g = delegate(int y) { System.Console.WriteLine(y); };
1038 }
1039 }",
1040 "foo.cs",
1041 |metric| {
1042 insta::assert_json_snapshot!(
1044 metric.nom,
1045 @r###"
1046 {
1047 "functions": 1.0,
1048 "closures": 2.0,
1049 "functions_average": 0.2,
1050 "closures_average": 0.4,
1051 "total": 3.0,
1052 "average": 0.6,
1053 "functions_min": 0.0,
1054 "functions_max": 1.0,
1055 "closures_min": 0.0,
1056 "closures_max": 1.0
1057 }"###
1058 );
1059 },
1060 );
1061 }
1062
1063 #[test]
1064 fn go_top_level_funcs() {
1065 check_metrics::<GoParser>(
1066 "package main
1067 func a() {}
1068 func b() {}
1069 func c() {}",
1070 "foo.go",
1071 |metric| {
1072 insta::assert_json_snapshot!(
1074 metric.nom,
1075 @r###"
1076 {
1077 "functions": 3.0,
1078 "closures": 0.0,
1079 "functions_average": 0.75,
1080 "closures_average": 0.0,
1081 "total": 3.0,
1082 "average": 0.75,
1083 "functions_min": 0.0,
1084 "functions_max": 1.0,
1085 "closures_min": 0.0,
1086 "closures_max": 0.0
1087 }"###
1088 );
1089 },
1090 );
1091 }
1092
1093 #[test]
1094 fn go_method_declaration() {
1095 check_metrics::<GoParser>(
1096 "package main
1097 type T struct{}
1098 func (r *T) M() {}",
1099 "foo.go",
1100 |metric| {
1101 insta::assert_json_snapshot!(
1103 metric.nom,
1104 @r###"
1105 {
1106 "functions": 1.0,
1107 "closures": 0.0,
1108 "functions_average": 0.5,
1109 "closures_average": 0.0,
1110 "total": 1.0,
1111 "average": 0.5,
1112 "functions_min": 0.0,
1113 "functions_max": 1.0,
1114 "closures_min": 0.0,
1115 "closures_max": 0.0
1116 }"###
1117 );
1118 },
1119 );
1120 }
1121
1122 #[test]
1123 fn go_func_literal_is_closure() {
1124 check_metrics::<GoParser>(
1125 "package main
1126 var f = func() {}",
1127 "foo.go",
1128 |metric| {
1129 insta::assert_json_snapshot!(
1131 metric.nom,
1132 @r###"
1133 {
1134 "functions": 0.0,
1135 "closures": 1.0,
1136 "functions_average": 0.0,
1137 "closures_average": 0.5,
1138 "total": 1.0,
1139 "average": 0.5,
1140 "functions_min": 0.0,
1141 "functions_max": 0.0,
1142 "closures_min": 0.0,
1143 "closures_max": 1.0
1144 }"###
1145 );
1146 },
1147 );
1148 }
1149
1150 #[test]
1151 fn go_nested_closures() {
1152 check_metrics::<GoParser>(
1153 "package main
1154 func f() {
1155 inner := func() {
1156 deeper := func() {}
1157 _ = deeper
1158 }
1159 _ = inner
1160 }",
1161 "foo.go",
1162 |metric| {
1163 insta::assert_json_snapshot!(
1165 metric.nom,
1166 @r###"
1167 {
1168 "functions": 1.0,
1169 "closures": 2.0,
1170 "functions_average": 0.25,
1171 "closures_average": 0.5,
1172 "total": 3.0,
1173 "average": 0.75,
1174 "functions_min": 0.0,
1175 "functions_max": 1.0,
1176 "closures_min": 0.0,
1177 "closures_max": 1.0
1178 }"###
1179 );
1180 },
1181 );
1182 }
1183
1184 #[test]
1185 fn java_closure_nom() {
1186 check_metrics::<JavaParser>(
1187 "interface printable{
1188 void print();
1189 }
1190
1191 interface IntFunc {
1192 int func(int n);
1193 }
1194
1195 class Printer implements printable{
1196 public void print(){System.out.println(\"Hello\");}
1197
1198 public static void main(String args[]){
1199 Printer obj = new Printer();
1200 obj.print();
1201 IntFunc meaning = (i) -> i + 42;
1202 int i = meaning.func(1);
1203 }
1204 }",
1205 "foo.java",
1206 |metric| {
1207 insta::assert_json_snapshot!(
1209 metric.nom,
1210 @r###"
1211 {
1212 "functions": 4.0,
1213 "closures": 1.0,
1214 "functions_average": 0.5,
1215 "closures_average": 0.125,
1216 "total": 5.0,
1217 "average": 0.625,
1218 "functions_min": 0.0,
1219 "functions_max": 1.0,
1220 "closures_min": 0.0,
1221 "closures_max": 1.0
1222 }"###
1223 );
1224 },
1225 );
1226 }
1227
1228 #[test]
1229 fn groovy_nom() {
1230 check_metrics::<GroovyParser>(
1231 "class Printer {
1232 void print() {
1233 println 'hello'
1234 }
1235 static void main(String[] args) {
1236 def p = new Printer()
1237 p.print()
1238 def doubler = { x -> x * 2 }
1239 int r = doubler(21)
1240 }
1241 }",
1242 "foo.groovy",
1243 |metric| {
1244 assert_eq!(metric.nom.functions_sum(), 2.0);
1251 assert_eq!(metric.nom.closures_sum(), 1.0);
1252 },
1253 );
1254 }
1255
1256 #[test]
1257 fn groovy_nom_function_definition() {
1258 check_metrics::<GroovyParser>(
1261 "def greet(name) {
1262 println(name)
1263 }
1264 greet('world')",
1265 "foo.groovy",
1266 |metric| {
1267 assert_eq!(metric.nom.functions_sum(), 1.0);
1268 },
1269 );
1270 }
1271
1272 #[test]
1273 fn perl_nom() {
1274 check_metrics::<PerlParser>(
1275 "sub a { 1 }
1276 sub b { 2 }
1277 my $c = sub { 3 };
1278 sub outer {
1279 my $inner = sub { 4 };
1280 return $inner;
1281 }",
1282 "foo.pl",
1283 |metric| {
1284 insta::assert_json_snapshot!(
1285 metric.nom,
1286 @r#"
1287 {
1288 "functions": 3.0,
1289 "closures": 2.0,
1290 "functions_average": 0.5,
1291 "closures_average": 0.3333333333333333,
1292 "total": 5.0,
1293 "average": 0.8333333333333334,
1294 "functions_min": 0.0,
1295 "functions_max": 1.0,
1296 "closures_min": 0.0,
1297 "closures_max": 1.0
1298 }
1299 "#
1300 );
1301 },
1302 );
1303 }
1304
1305 #[test]
1306 fn tsx_named_and_arrow_functions() {
1307 check_metrics::<TsxParser>(
1308 "function greet(name: string): string {
1309 return `Hello, ${name}`;
1310 }
1311 const add = (a: number, b: number) => a + b;
1312 const log = () => { console.log('done'); };",
1313 "foo.tsx",
1314 |metric| {
1315 insta::assert_json_snapshot!(
1316 metric.nom,
1317 @r###"
1318 {
1319 "functions": 3.0,
1320 "closures": 0.0,
1321 "functions_average": 0.75,
1322 "closures_average": 0.0,
1323 "total": 3.0,
1324 "average": 0.75,
1325 "functions_min": 0.0,
1326 "functions_max": 1.0,
1327 "closures_min": 0.0,
1328 "closures_max": 0.0
1329 }"###
1330 );
1331 },
1332 );
1333 }
1334
1335 #[test]
1336 fn typescript_named_arrow_and_class_methods() {
1337 check_metrics::<TypescriptParser>(
1338 "function compute(x: number): number {
1339 return x * 2;
1340 }
1341 const double = (n: number): number => n * 2;
1342 class Calculator {
1343 add(a: number, b: number): number {
1344 return a + b;
1345 }
1346 }",
1347 "foo.ts",
1348 |metric| {
1349 insta::assert_json_snapshot!(
1350 metric.nom,
1351 @r###"
1352 {
1353 "functions": 3.0,
1354 "closures": 0.0,
1355 "functions_average": 0.6,
1356 "closures_average": 0.0,
1357 "total": 3.0,
1358 "average": 0.6,
1359 "functions_min": 0.0,
1360 "functions_max": 1.0,
1361 "closures_min": 0.0,
1362 "closures_max": 0.0
1363 }"###
1364 );
1365 },
1366 );
1367 }
1368
1369 #[test]
1370 fn mozjs_nom() {
1371 check_metrics::<MozjsParser>(
1372 "function f(a, b) {
1373 function foo(a) {
1374 return a;
1375 }
1376 var bar = (function () {
1377 var counter = 0;
1378 return function () {
1379 counter += 1;
1380 return counter
1381 }
1382 })();
1383 return bar(foo(a), a);
1384 }",
1385 "foo.js",
1386 |metric| {
1387 insta::assert_json_snapshot!(
1388 metric.nom,
1389 @r###"
1390 {
1391 "functions": 3.0,
1392 "closures": 1.0,
1393 "functions_average": 0.6,
1394 "closures_average": 0.2,
1395 "total": 4.0,
1396 "average": 0.8,
1397 "functions_min": 0.0,
1398 "functions_max": 1.0,
1399 "closures_min": 0.0,
1400 "closures_max": 1.0
1401 }"###
1402 );
1403 },
1404 );
1405 }
1406
1407 #[test]
1408 fn mozjs_arrow_and_method() {
1409 check_metrics::<MozjsParser>(
1410 "let add = (a, b) => a + b;
1411 class Counter {
1412 increment() {
1413 this.count++;
1414 }
1415 }",
1416 "foo.js",
1417 |metric| {
1418 insta::assert_json_snapshot!(
1419 metric.nom,
1420 @r###"
1421 {
1422 "functions": 2.0,
1423 "closures": 0.0,
1424 "functions_average": 0.5,
1425 "closures_average": 0.0,
1426 "total": 2.0,
1427 "average": 0.5,
1428 "functions_min": 0.0,
1429 "functions_max": 1.0,
1430 "closures_min": 0.0,
1431 "closures_max": 0.0
1432 }"###
1433 );
1434 },
1435 );
1436 }
1437
1438 #[test]
1439 fn kotlin_nom_class_with_methods() {
1440 check_metrics::<KotlinParser>(
1441 "class Calculator {
1442 fun add(a: Int, b: Int): Int {
1443 return a + b
1444 }
1445 fun subtract(a: Int, b: Int): Int {
1446 return a - b
1447 }
1448 }",
1449 "foo.kt",
1450 |metric| {
1451 insta::assert_json_snapshot!(
1452 metric.nom,
1453 @r###"
1454 {
1455 "functions": 2.0,
1456 "closures": 0.0,
1457 "functions_average": 0.5,
1458 "closures_average": 0.0,
1459 "total": 2.0,
1460 "average": 0.5,
1461 "functions_min": 0.0,
1462 "functions_max": 1.0,
1463 "closures_min": 0.0,
1464 "closures_max": 0.0
1465 }
1466 "###
1467 );
1468 },
1469 );
1470 }
1471
1472 #[test]
1473 fn lua_nom() {
1474 check_metrics::<LuaParser>(
1475 "function greet(name)
1476 return \"hello \" .. name
1477end
1478
1479local add = function(a, b)
1480 return a + b
1481end
1482
1483local function outer()
1484 local inner = function()
1485 return 42
1486 end
1487 return inner()
1488end",
1489 "foo.lua",
1490 |metric| {
1491 insta::assert_json_snapshot!(metric.nom, @r###"
1493 {
1494 "functions": 2.0,
1495 "closures": 2.0,
1496 "functions_average": 0.4,
1497 "closures_average": 0.4,
1498 "total": 4.0,
1499 "average": 0.8,
1500 "functions_min": 0.0,
1501 "functions_max": 1.0,
1502 "closures_min": 0.0,
1503 "closures_max": 1.0
1504 }
1505 "###);
1506 },
1507 );
1508 }
1509
1510 #[test]
1511 fn bash_nom() {
1512 check_metrics::<BashParser>(
1513 "#!/bin/bash
1514foo() {
1515 echo 'hello'
1516}
1517bar() {
1518 echo 'world'
1519}
1520foo
1521bar",
1522 "foo.sh",
1523 |metric| {
1524 insta::assert_json_snapshot!(
1525 metric.nom,
1526 @r#"
1527 {
1528 "functions": 2.0,
1529 "closures": 0.0,
1530 "functions_average": 0.6666666666666666,
1531 "closures_average": 0.0,
1532 "total": 2.0,
1533 "average": 0.6666666666666666,
1534 "functions_min": 0.0,
1535 "functions_max": 1.0,
1536 "closures_min": 0.0,
1537 "closures_max": 0.0
1538 }
1539 "#
1540 );
1541 },
1542 );
1543 }
1544
1545 #[test]
1546 fn tcl_nom() {
1547 check_metrics::<TclParser>(
1548 "proc foo {a} { puts $a }
1549proc bar {x y} { puts $x }
1550foo 1
1551bar 2 3",
1552 "foo.tcl",
1553 |metric| {
1554 assert_eq!(metric.nom.functions_sum(), 2.0);
1555 assert_eq!(metric.nom.closures_sum(), 0.0);
1556 insta::assert_json_snapshot!(metric.nom);
1557 },
1558 );
1559 }
1560
1561 #[test]
1562 fn tcl_nested_nom() {
1563 check_metrics::<TclParser>(
1564 "proc outer {a} {
1565 proc inner {x} { puts $x }
1566 inner $a
1567}",
1568 "foo.tcl",
1569 |metric| {
1570 assert_eq!(metric.nom.functions_sum(), 2.0);
1571 assert_eq!(metric.nom.closures_sum(), 0.0);
1572 insta::assert_json_snapshot!(metric.nom);
1573 },
1574 );
1575 }
1576
1577 #[test]
1578 fn typescript_class_methods() {
1579 check_metrics::<TypescriptParser>(
1580 "class Calc {
1581 add(a: number, b: number): number { return a + b; }
1582 sub(a: number, b: number): number { return a - b; }
1583 }",
1584 "foo.ts",
1585 |metric| {
1586 assert_eq!(metric.nom.functions_sum(), 2.0);
1587 assert_eq!(metric.nom.closures_sum(), 0.0);
1588 insta::assert_json_snapshot!(metric.nom);
1589 },
1590 );
1591 }
1592
1593 #[test]
1594 fn typescript_arrow_and_function() {
1595 check_metrics::<TypescriptParser>(
1596 "function f(): number { return 1; }
1597 const g = (): number => 2;
1598 const h = (x: number): number => x * 2;",
1599 "foo.ts",
1600 |metric| {
1601 assert_eq!(metric.nom.functions_sum(), 3.0);
1602 assert_eq!(metric.nom.closures_sum(), 0.0);
1603 insta::assert_json_snapshot!(metric.nom);
1604 },
1605 );
1606 }
1607
1608 #[test]
1609 fn tsx_class_methods() {
1610 check_metrics::<TsxParser>(
1611 "class Calc {
1612 add(a: number, b: number): number { return a + b; }
1613 sub(a: number, b: number): number { return a - b; }
1614 }",
1615 "foo.tsx",
1616 |metric| {
1617 assert_eq!(metric.nom.functions_sum(), 2.0);
1618 assert_eq!(metric.nom.closures_sum(), 0.0);
1619 insta::assert_json_snapshot!(metric.nom);
1620 },
1621 );
1622 }
1623
1624 #[test]
1625 fn tsx_arrow_and_function() {
1626 check_metrics::<TsxParser>(
1627 "function f(): number { return 1; }
1628 const g = (): number => 2;
1629 const h = (x: number): number => x * 2;",
1630 "foo.tsx",
1631 |metric| {
1632 assert_eq!(metric.nom.functions_sum(), 3.0);
1633 assert_eq!(metric.nom.closures_sum(), 0.0);
1634 insta::assert_json_snapshot!(metric.nom);
1635 },
1636 );
1637 }
1638
1639 #[test]
1640 fn bash_multiple_functions_nom() {
1641 check_metrics::<BashParser>(
1642 "#!/bin/bash
1643f() {
1644 echo hello
1645}
1646g() {
1647 echo world
1648}",
1649 "foo.sh",
1650 |metric| {
1651 assert_eq!(metric.nom.functions_sum(), 2.0);
1652 assert_eq!(metric.nom.closures_sum(), 0.0);
1653 insta::assert_json_snapshot!(metric.nom);
1654 },
1655 );
1656 }
1657
1658 #[test]
1659 fn bash_nested_functions_nom() {
1660 check_metrics::<BashParser>(
1661 "#!/bin/bash
1662outer() {
1663 inner() {
1664 echo inner
1665 }
1666 inner
1667}",
1668 "foo.sh",
1669 |metric| {
1670 assert_eq!(metric.nom.functions_sum(), 2.0);
1671 assert_eq!(metric.nom.closures_sum(), 0.0);
1672 insta::assert_json_snapshot!(metric.nom);
1673 },
1674 );
1675 }
1676
1677 #[test]
1678 fn mozjs_nested_function_nom() {
1679 check_metrics::<MozjsParser>(
1680 "function outer() {
1681 function inner() {
1682 return 1;
1683 }
1684 return inner();
1685 }",
1686 "foo.js",
1687 |metric| {
1688 assert_eq!(metric.nom.functions_sum(), 2.0);
1689 assert_eq!(metric.nom.closures_sum(), 0.0);
1690 insta::assert_json_snapshot!(metric.nom);
1691 },
1692 );
1693 }
1694
1695 #[test]
1696 fn mozjs_class_methods_nom() {
1697 check_metrics::<MozjsParser>(
1698 "class Calc {
1699 add(a, b) { return a + b; }
1700 sub(a, b) { return a - b; }
1701 }",
1702 "foo.js",
1703 |metric| {
1704 assert_eq!(metric.nom.functions_sum(), 2.0);
1705 assert_eq!(metric.nom.closures_sum(), 0.0);
1706 insta::assert_json_snapshot!(metric.nom);
1707 },
1708 );
1709 }
1710
1711 #[test]
1712 fn mozjs_iife_nom() {
1713 check_metrics::<MozjsParser>(
1714 "(function() {
1715 var x = 1;
1716 return x;
1717 })();",
1718 "foo.js",
1719 |metric| {
1720 assert_eq!(metric.nom.functions_sum(), 0.0);
1721 assert_eq!(metric.nom.closures_sum(), 1.0);
1722 insta::assert_json_snapshot!(metric.nom);
1723 },
1724 );
1725 }
1726
1727 #[test]
1728 fn kotlin_class_methods_nom() {
1729 check_metrics::<KotlinParser>(
1730 "class Calc {
1731 fun add(a: Int, b: Int): Int = a + b
1732 fun sub(a: Int, b: Int): Int = a - b
1733 }",
1734 "foo.kt",
1735 |metric| {
1736 assert_eq!(metric.nom.functions_sum(), 2.0);
1737 assert_eq!(metric.nom.closures_sum(), 0.0);
1738 insta::assert_json_snapshot!(metric.nom);
1739 },
1740 );
1741 }
1742
1743 #[test]
1744 fn kotlin_lambda_nom() {
1745 check_metrics::<KotlinParser>(
1746 "fun f(list: List<Int>): Int {
1747 val double = { x: Int -> x * 2 }
1748 return list.sumOf(double)
1749 }",
1750 "foo.kt",
1751 |metric| {
1752 assert_eq!(metric.nom.functions_sum(), 1.0);
1753 assert_eq!(metric.nom.closures_sum(), 1.0);
1754 insta::assert_json_snapshot!(metric.nom);
1755 },
1756 );
1757 }
1758
1759 #[test]
1760 fn php_nom() {
1761 check_metrics::<PhpParser>(
1764 "<?php
1765 function top(): void {}
1766 class A {
1767 public function m1(): void {}
1768 public function m2(): int {
1769 $f = function () { return 1; };
1770 $g = fn () => 2;
1771 return $f() + $g();
1772 }
1773 }",
1774 "foo.php",
1775 |metric| {
1776 insta::assert_json_snapshot!(
1777 metric.nom,
1778 @r###"
1779 {
1780 "functions": 3.0,
1781 "closures": 2.0,
1782 "functions_average": 0.42857142857142855,
1783 "closures_average": 0.2857142857142857,
1784 "total": 5.0,
1785 "average": 0.7142857142857143,
1786 "functions_min": 0.0,
1787 "functions_max": 1.0,
1788 "closures_min": 0.0,
1789 "closures_max": 1.0
1790 }"###
1791 );
1792 },
1793 );
1794 }
1795
1796 #[test]
1797 fn php_nom_anonymous_class() {
1798 check_metrics::<PhpParser>(
1802 "<?php
1803 function f(): object {
1804 return new class {
1805 public function inner(): int { return 1; }
1806 };
1807 }",
1808 "foo.php",
1809 |metric| {
1810 insta::assert_json_snapshot!(
1811 metric.nom,
1812 @r###"
1813 {
1814 "functions": 2.0,
1815 "closures": 0.0,
1816 "functions_average": 0.5,
1817 "closures_average": 0.0,
1818 "total": 2.0,
1819 "average": 0.5,
1820 "functions_min": 0.0,
1821 "functions_max": 1.0,
1822 "closures_min": 0.0,
1823 "closures_max": 0.0
1824 }"###
1825 );
1826 },
1827 );
1828 }
1829
1830 #[test]
1839 fn elixir_default_nom_counts_only_anonymous_functions() {
1840 check_metrics::<ElixirParser>(
1841 "defmodule Foo do\n def public_fn(x), do: x + 1\n defp private_fn(x), do: x - 1\n def with_anon do\n inc = fn x -> x + 1 end\n dec = fn x -> x - 1 end\n {inc, dec}\n end\nend\n",
1842 "foo.ex",
1843 |metric| {
1844 assert_eq!(metric.nom.functions_sum(), 0.0);
1845 assert_eq!(metric.nom.closures_sum(), 2.0);
1846 },
1847 );
1848 }
1849
1850 #[test]
1851 fn stats_display_commas_between_all_fields() {
1852 let stats = Stats {
1853 functions: 0,
1854 closures: 0,
1855 functions_sum: 3,
1856 closures_sum: 1,
1857 functions_min: 0,
1858 functions_max: 2,
1859 closures_min: 0,
1860 closures_max: 1,
1861 space_count: 2,
1862 };
1863 let formatted = format!("{stats}");
1864
1865 let expected_fragments = [
1868 "functions: 3, closures: 1",
1869 "closures: 1, functions_average:",
1870 "functions_average: 1.5, closures_average:",
1871 "closures_average: 0.5, total:",
1872 "total: 4, average:",
1873 "average: 2, functions_min:",
1874 "functions_min: 0, functions_max:",
1875 "functions_max: 2, closures_min:",
1876 "closures_min: 0, closures_max:",
1877 ];
1878
1879 for fragment in expected_fragments {
1880 assert!(
1881 formatted.contains(fragment),
1882 "missing fragment {fragment:?} in: {formatted}"
1883 );
1884 }
1885 }
1886
1887 #[test]
1888 fn ruby_nom() {
1889 check_metrics::<RubyParser>(
1895 "class C\n def add(a, b)\n a + b\n end\n def mul(a, b)\n a * b\n end\n def self.factory\n new\n end\nend\n\n[1, 2, 3].each { |x| puts x }\n",
1896 "foo.rb",
1897 |metric| {
1898 assert_eq!(metric.nom.functions_sum(), 3.0);
1899 assert_eq!(metric.nom.closures_sum(), 1.0);
1900 assert_eq!(metric.nom.total(), 4.0);
1901 },
1902 );
1903 }
1904}