1use serde::Serialize;
2use serde::ser::{SerializeStruct, Serializer};
3use std::fmt;
4
5use crate::checker::Checker;
6use crate::macros::implement_metric_trait;
7use crate::*;
8
9#[derive(Debug, Clone)]
14pub struct Stats {
15 fn_nargs: usize,
16 closure_nargs: usize,
17 fn_nargs_sum: usize,
18 closure_nargs_sum: usize,
19 fn_nargs_min: usize,
20 closure_nargs_min: usize,
21 fn_nargs_max: usize,
22 closure_nargs_max: usize,
23 total_functions: usize,
24 total_closures: usize,
25}
26
27impl Default for Stats {
28 fn default() -> Self {
29 Self {
30 fn_nargs: 0,
31 closure_nargs: 0,
32 fn_nargs_sum: 0,
33 closure_nargs_sum: 0,
34 fn_nargs_min: usize::MAX,
35 closure_nargs_min: usize::MAX,
36 fn_nargs_max: 0,
37 closure_nargs_max: 0,
38 total_functions: 0,
39 total_closures: 0,
40 }
41 }
42}
43
44impl Serialize for Stats {
45 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
46 where
47 S: Serializer,
48 {
49 let mut st = serializer.serialize_struct("nargs", 10)?;
50 st.serialize_field("total_functions", &self.fn_args_sum())?;
51 st.serialize_field("total_closures", &self.closure_args_sum())?;
52 st.serialize_field("average_functions", &self.fn_args_average())?;
53 st.serialize_field("average_closures", &self.closure_args_average())?;
54 st.serialize_field("total", &self.nargs_total())?;
55 st.serialize_field("average", &self.nargs_average())?;
56 st.serialize_field("functions_min", &self.fn_args_min())?;
57 st.serialize_field("functions_max", &self.fn_args_max())?;
58 st.serialize_field("closures_min", &self.closure_args_min())?;
59 st.serialize_field("closures_max", &self.closure_args_max())?;
60 st.end()
61 }
62}
63
64impl fmt::Display for Stats {
65 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
66 write!(
67 f,
68 "total_functions: {}, total_closures: {}, average_functions: {}, average_closures: {}, total: {}, average: {}, functions_min: {}, functions_max: {}, closures_min: {}, closures_max: {}",
69 self.fn_args(),
70 self.closure_args(),
71 self.fn_args_average(),
72 self.closure_args_average(),
73 self.nargs_total(),
74 self.nargs_average(),
75 self.fn_args_min(),
76 self.fn_args_max(),
77 self.closure_args_min(),
78 self.closure_args_max()
79 )
80 }
81}
82
83impl Stats {
84 pub fn merge(&mut self, other: &Stats) {
86 self.closure_nargs_min = self.closure_nargs_min.min(other.closure_nargs_min);
87 self.closure_nargs_max = self.closure_nargs_max.max(other.closure_nargs_max);
88 self.fn_nargs_min = self.fn_nargs_min.min(other.fn_nargs_min);
89 self.fn_nargs_max = self.fn_nargs_max.max(other.fn_nargs_max);
90 self.fn_nargs_sum += other.fn_nargs_sum;
91 self.closure_nargs_sum += other.closure_nargs_sum;
92 }
93
94 #[inline(always)]
96 pub fn fn_args(&self) -> f64 {
97 self.fn_nargs as f64
98 }
99
100 #[inline(always)]
102 pub fn closure_args(&self) -> f64 {
103 self.closure_nargs as f64
104 }
105
106 #[inline(always)]
108 pub fn fn_args_sum(&self) -> f64 {
109 self.fn_nargs_sum as f64
110 }
111
112 #[inline(always)]
114 pub fn closure_args_sum(&self) -> f64 {
115 self.closure_nargs_sum as f64
116 }
117
118 #[inline(always)]
120 pub fn fn_args_average(&self) -> f64 {
121 self.fn_nargs_sum as f64 / self.total_functions.max(1) as f64
122 }
123
124 #[inline(always)]
126 pub fn closure_args_average(&self) -> f64 {
127 self.closure_nargs_sum as f64 / self.total_closures.max(1) as f64
128 }
129
130 #[inline(always)]
133 pub fn nargs_total(&self) -> f64 {
134 self.fn_args_sum() + self.closure_args_sum()
135 }
136
137 #[inline(always)]
142 pub fn nargs_average(&self) -> f64 {
143 self.nargs_total() / (self.total_functions + self.total_closures).max(1) as f64
144 }
145 #[inline(always)]
147 pub fn fn_args_min(&self) -> f64 {
148 self.fn_nargs_min as f64
149 }
150 #[inline(always)]
152 pub fn fn_args_max(&self) -> f64 {
153 self.fn_nargs_max as f64
154 }
155 #[inline(always)]
157 pub fn closure_args_min(&self) -> f64 {
158 self.closure_nargs_min as f64
159 }
160 #[inline(always)]
162 pub fn closure_args_max(&self) -> f64 {
163 self.closure_nargs_max as f64
164 }
165 #[inline(always)]
166 pub(crate) fn compute_sum(&mut self) {
167 self.closure_nargs_sum += self.closure_nargs;
168 self.fn_nargs_sum += self.fn_nargs;
169 }
170 #[inline(always)]
171 pub(crate) fn compute_minmax(&mut self) {
172 self.closure_nargs_min = self.closure_nargs_min.min(self.closure_nargs);
173 self.closure_nargs_max = self.closure_nargs_max.max(self.closure_nargs);
174 self.fn_nargs_min = self.fn_nargs_min.min(self.fn_nargs);
175 self.fn_nargs_max = self.fn_nargs_max.max(self.fn_nargs);
176 self.compute_sum();
177 }
178 pub(crate) fn finalize(&mut self, total_functions: usize, total_closures: usize) {
179 self.total_functions = total_functions;
180 self.total_closures = total_closures;
181 }
182}
183
184#[inline(always)]
185fn compute_args<T: Checker>(node: &Node, nargs: &mut usize) {
186 if let Some(params) = node.child_by_field_name("parameters") {
187 let node_params = params;
188 node_params.act_on_child(&mut |n| {
189 if !T::is_non_arg(n) {
190 *nargs += 1;
191 }
192 });
193 }
194}
195
196pub trait NArgs
197where
198 Self: Checker,
199 Self: std::marker::Sized,
200{
201 fn compute(node: &Node, stats: &mut Stats) {
202 if Self::is_func(node) {
203 compute_args::<Self>(node, &mut stats.fn_nargs);
204 return;
205 }
206
207 if Self::is_closure(node) {
208 compute_args::<Self>(node, &mut stats.closure_nargs);
209 }
210 }
211}
212
213impl NArgs for CppCode {
214 fn compute(node: &Node, stats: &mut Stats) {
215 if Self::is_func(node) {
216 if let Some(declarator) = node.child_by_field_name("declarator") {
217 let new_node = declarator;
218 compute_args::<Self>(&new_node, &mut stats.fn_nargs);
219 }
220 return;
221 }
222
223 if Self::is_closure(node)
224 && let Some(declarator) = node.child_by_field_name("declarator")
225 {
226 let new_node = declarator;
227 compute_args::<Self>(&new_node, &mut stats.closure_nargs);
228 }
229 }
230}
231
232implement_metric_trait!(
233 [NArgs],
234 PythonCode,
235 JavascriptCode,
236 TypescriptCode,
237 TsxCode,
238 RustCode,
239 PreprocCode,
240 CcommentCode,
241 JavaCode,
242 KotlinCode
243);
244
245#[cfg(test)]
246mod tests {
247 use crate::tools::check_metrics;
248
249 use super::*;
250
251 #[test]
252 fn python_no_functions_and_closures() {
253 check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
254 insta::assert_json_snapshot!(
256 metric.nargs,
257 @r###"
258 {
259 "total_functions": 0.0,
260 "total_closures": 0.0,
261 "average_functions": 0.0,
262 "average_closures": 0.0,
263 "total": 0.0,
264 "average": 0.0,
265 "functions_min": 0.0,
266 "functions_max": 0.0,
267 "closures_min": 0.0,
268 "closures_max": 0.0
269 }"###
270 );
271 });
272 }
273
274 #[test]
275 fn rust_no_functions_and_closures() {
276 check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
277 insta::assert_json_snapshot!(
279 metric.nargs,
280 @r###"
281 {
282 "total_functions": 0.0,
283 "total_closures": 0.0,
284 "average_functions": 0.0,
285 "average_closures": 0.0,
286 "total": 0.0,
287 "average": 0.0,
288 "functions_min": 0.0,
289 "functions_max": 0.0,
290 "closures_min": 0.0,
291 "closures_max": 0.0
292 }"###
293 );
294 });
295 }
296
297 #[test]
298 fn cpp_no_functions_and_closures() {
299 check_metrics::<CppParser>("int a = 42;", "foo.cpp", |metric| {
300 insta::assert_json_snapshot!(
302 metric.nargs,
303 @r###"
304 {
305 "total_functions": 0.0,
306 "total_closures": 0.0,
307 "average_functions": 0.0,
308 "average_closures": 0.0,
309 "total": 0.0,
310 "average": 0.0,
311 "functions_min": 0.0,
312 "functions_max": 0.0,
313 "closures_min": 0.0,
314 "closures_max": 0.0
315 }"###
316 );
317 });
318 }
319
320 #[test]
321 fn javascript_no_functions_and_closures() {
322 check_metrics::<JavascriptParser>("var a = 42;", "foo.js", |metric| {
323 insta::assert_json_snapshot!(
325 metric.nargs,
326 @r###"
327 {
328 "total_functions": 0.0,
329 "total_closures": 0.0,
330 "average_functions": 0.0,
331 "average_closures": 0.0,
332 "total": 0.0,
333 "average": 0.0,
334 "functions_min": 0.0,
335 "functions_max": 0.0,
336 "closures_min": 0.0,
337 "closures_max": 0.0
338 }"###
339 );
340 });
341 }
342
343 #[test]
344 fn python_single_function() {
345 check_metrics::<PythonParser>(
346 "def f(a, b):
347 if a:
348 return a",
349 "foo.py",
350 |metric| {
351 insta::assert_json_snapshot!(
353 metric.nargs,
354 @r###"
355 {
356 "total_functions": 2.0,
357 "total_closures": 0.0,
358 "average_functions": 2.0,
359 "average_closures": 0.0,
360 "total": 2.0,
361 "average": 2.0,
362 "functions_min": 0.0,
363 "functions_max": 2.0,
364 "closures_min": 0.0,
365 "closures_max": 0.0
366 }"###
367 );
368 },
369 );
370 }
371
372 #[test]
373 fn rust_single_function() {
374 check_metrics::<RustParser>(
375 "fn f(a: bool, b: usize) {
376 if a {
377 return a;
378 }
379 }",
380 "foo.rs",
381 |metric| {
382 insta::assert_json_snapshot!(
384 metric.nargs,
385 @r###"
386 {
387 "total_functions": 2.0,
388 "total_closures": 0.0,
389 "average_functions": 2.0,
390 "average_closures": 0.0,
391 "total": 2.0,
392 "average": 2.0,
393 "functions_min": 0.0,
394 "functions_max": 2.0,
395 "closures_min": 0.0,
396 "closures_max": 0.0
397 }"###
398 );
399 },
400 );
401 }
402
403 #[test]
404 fn c_single_function() {
405 check_metrics::<CppParser>(
406 "int f(int a, int b) {
407 if (a) {
408 return a;
409 }
410 }",
411 "foo.c",
412 |metric| {
413 insta::assert_json_snapshot!(
415 metric.nargs,
416 @r###"
417 {
418 "total_functions": 2.0,
419 "total_closures": 0.0,
420 "average_functions": 2.0,
421 "average_closures": 0.0,
422 "total": 2.0,
423 "average": 2.0,
424 "functions_min": 0.0,
425 "functions_max": 2.0,
426 "closures_min": 0.0,
427 "closures_max": 0.0
428 }"###
429 );
430 },
431 );
432 }
433
434 #[test]
435 fn javascript_single_function() {
436 check_metrics::<JavascriptParser>(
437 "function f(a, b) {
438 return a * b;
439 }",
440 "foo.js",
441 |metric| {
442 insta::assert_json_snapshot!(
444 metric.nargs,
445 @r###"
446 {
447 "total_functions": 2.0,
448 "total_closures": 0.0,
449 "average_functions": 2.0,
450 "average_closures": 0.0,
451 "total": 2.0,
452 "average": 2.0,
453 "functions_min": 0.0,
454 "functions_max": 2.0,
455 "closures_min": 0.0,
456 "closures_max": 0.0
457 }"###
458 );
459 },
460 );
461 }
462
463 #[test]
464 fn python_single_lambda() {
465 check_metrics::<PythonParser>("bar = lambda a: True", "foo.py", |metric| {
466 insta::assert_json_snapshot!(
468 metric.nargs,
469 @r###"
470 {
471 "total_functions": 0.0,
472 "total_closures": 1.0,
473 "average_functions": 0.0,
474 "average_closures": 1.0,
475 "total": 1.0,
476 "average": 1.0,
477 "functions_min": 0.0,
478 "functions_max": 0.0,
479 "closures_min": 1.0,
480 "closures_max": 1.0
481 }"###
482 );
483 });
484 }
485
486 #[test]
487 fn rust_single_closure() {
488 check_metrics::<RustParser>("let bar = |i: i32| -> i32 { i + 1 };", "foo.rs", |metric| {
489 insta::assert_json_snapshot!(
491 metric.nargs,
492 @r###"
493 {
494 "total_functions": 0.0,
495 "total_closures": 1.0,
496 "average_functions": 0.0,
497 "average_closures": 1.0,
498 "total": 1.0,
499 "average": 1.0,
500 "functions_min": 0.0,
501 "functions_max": 0.0,
502 "closures_min": 0.0,
503 "closures_max": 1.0
504 }"###
505 );
506 });
507 }
508
509 #[test]
510 fn cpp_single_lambda() {
511 check_metrics::<CppParser>(
512 "auto bar = [](int x, int y) -> int { return x + y; };",
513 "foo.cpp",
514 |metric| {
515 insta::assert_json_snapshot!(
517 metric.nargs,
518 @r###"
519 {
520 "total_functions": 0.0,
521 "total_closures": 2.0,
522 "average_functions": 0.0,
523 "average_closures": 2.0,
524 "total": 2.0,
525 "average": 2.0,
526 "functions_min": 0.0,
527 "functions_max": 0.0,
528 "closures_min": 2.0,
529 "closures_max": 2.0
530 }"###
531 );
532 },
533 );
534 }
535
536 #[test]
537 fn javascript_single_closure() {
538 check_metrics::<JavascriptParser>("function (a, b) {return a + b};", "foo.js", |metric| {
539 insta::assert_json_snapshot!(
541 metric.nargs,
542 @r###"
543 {
544 "total_functions": 0.0,
545 "total_closures": 2.0,
546 "average_functions": 0.0,
547 "average_closures": 2.0,
548 "total": 2.0,
549 "average": 2.0,
550 "functions_min": 0.0,
551 "functions_max": 0.0,
552 "closures_min": 0.0,
553 "closures_max": 2.0
554 }"###
555 );
556 });
557 }
558
559 #[test]
560 fn python_functions() {
561 check_metrics::<PythonParser>(
562 "def f(a, b):
563 if a:
564 return a
565 def f(a, b):
566 if b:
567 return b",
568 "foo.py",
569 |metric| {
570 insta::assert_json_snapshot!(
572 metric.nargs,
573 @r###"
574 {
575 "total_functions": 4.0,
576 "total_closures": 0.0,
577 "average_functions": 2.0,
578 "average_closures": 0.0,
579 "total": 4.0,
580 "average": 2.0,
581 "functions_min": 0.0,
582 "functions_max": 2.0,
583 "closures_min": 0.0,
584 "closures_max": 0.0
585 }"###
586 );
587 },
588 );
589
590 check_metrics::<PythonParser>(
591 "def f(a, b):
592 if a:
593 return a
594 def f(a, b, c):
595 if b:
596 return b",
597 "foo.py",
598 |metric| {
599 insta::assert_json_snapshot!(
601 metric.nargs,
602 @r###"
603 {
604 "total_functions": 5.0,
605 "total_closures": 0.0,
606 "average_functions": 2.5,
607 "average_closures": 0.0,
608 "total": 5.0,
609 "average": 2.5,
610 "functions_min": 0.0,
611 "functions_max": 3.0,
612 "closures_min": 0.0,
613 "closures_max": 0.0
614 }"###
615 );
616 },
617 );
618 }
619
620 #[test]
621 fn rust_functions() {
622 check_metrics::<RustParser>(
623 "fn f(a: bool, b: usize) {
624 if a {
625 return a;
626 }
627 }
628 fn f1(a: bool, b: usize) {
629 if a {
630 return a;
631 }
632 }",
633 "foo.rs",
634 |metric| {
635 insta::assert_json_snapshot!(
637 metric.nargs,
638 @r###"
639 {
640 "total_functions": 4.0,
641 "total_closures": 0.0,
642 "average_functions": 2.0,
643 "average_closures": 0.0,
644 "total": 4.0,
645 "average": 2.0,
646 "functions_min": 0.0,
647 "functions_max": 2.0,
648 "closures_min": 0.0,
649 "closures_max": 0.0
650 }"###
651 );
652 },
653 );
654
655 check_metrics::<RustParser>(
656 "fn f(a: bool, b: usize) {
657 if a {
658 return a;
659 }
660 }
661 fn f1(a: bool, b: usize, c: usize) {
662 if a {
663 return a;
664 }
665 }",
666 "foo.rs",
667 |metric| {
668 insta::assert_json_snapshot!(
670 metric.nargs,
671 @r###"
672 {
673 "total_functions": 5.0,
674 "total_closures": 0.0,
675 "average_functions": 2.5,
676 "average_closures": 0.0,
677 "total": 5.0,
678 "average": 2.5,
679 "functions_min": 0.0,
680 "functions_max": 3.0,
681 "closures_min": 0.0,
682 "closures_max": 0.0
683 }"###
684 );
685 },
686 );
687 }
688
689 #[test]
690 fn c_functions() {
691 check_metrics::<CppParser>(
692 "int f(int a, int b) {
693 if (a) {
694 return a;
695 }
696 }
697 int f1(int a, int b) {
698 if (a) {
699 return a;
700 }
701 }",
702 "foo.c",
703 |metric| {
704 insta::assert_json_snapshot!(
706 metric.nargs,
707 @r###"
708 {
709 "total_functions": 4.0,
710 "total_closures": 0.0,
711 "average_functions": 2.0,
712 "average_closures": 0.0,
713 "total": 4.0,
714 "average": 2.0,
715 "functions_min": 0.0,
716 "functions_max": 2.0,
717 "closures_min": 0.0,
718 "closures_max": 0.0
719 }"###
720 );
721 },
722 );
723
724 check_metrics::<CppParser>(
725 "int f(int a, int b) {
726 if (a) {
727 return a;
728 }
729 }
730 int f1(int a, int b, int c) {
731 if (a) {
732 return a;
733 }
734 }",
735 "foo.c",
736 |metric| {
737 insta::assert_json_snapshot!(
739 metric.nargs,
740 @r###"
741 {
742 "total_functions": 5.0,
743 "total_closures": 0.0,
744 "average_functions": 2.5,
745 "average_closures": 0.0,
746 "total": 5.0,
747 "average": 2.5,
748 "functions_min": 0.0,
749 "functions_max": 3.0,
750 "closures_min": 0.0,
751 "closures_max": 0.0
752 }"###
753 );
754 },
755 );
756 }
757
758 #[test]
759 fn javascript_functions() {
760 check_metrics::<JavascriptParser>(
761 "function f(a, b) {
762 return a * b;
763 }
764 function f1(a, b) {
765 return a * b;
766 }",
767 "foo.js",
768 |metric| {
769 insta::assert_json_snapshot!(
771 metric.nargs,
772 @r###"
773 {
774 "total_functions": 4.0,
775 "total_closures": 0.0,
776 "average_functions": 2.0,
777 "average_closures": 0.0,
778 "total": 4.0,
779 "average": 2.0,
780 "functions_min": 0.0,
781 "functions_max": 2.0,
782 "closures_min": 0.0,
783 "closures_max": 0.0
784 }"###
785 );
786 },
787 );
788
789 check_metrics::<JavascriptParser>(
790 "function f(a, b) {
791 return a * b;
792 }
793 function f1(a, b, c) {
794 return a * b;
795 }",
796 "foo.js",
797 |metric| {
798 insta::assert_json_snapshot!(
800 metric.nargs,
801 @r###"
802 {
803 "total_functions": 5.0,
804 "total_closures": 0.0,
805 "average_functions": 2.5,
806 "average_closures": 0.0,
807 "total": 5.0,
808 "average": 2.5,
809 "functions_min": 0.0,
810 "functions_max": 3.0,
811 "closures_min": 0.0,
812 "closures_max": 0.0
813 }"###
814 );
815 },
816 );
817 }
818
819 #[test]
820 fn python_nested_functions() {
821 check_metrics::<PythonParser>(
822 "def f(a, b):
823 def foo(a):
824 if a:
825 return 1
826 bar = lambda a: lambda b: b or True or True
827 return bar(foo(a))(a)",
828 "foo.py",
829 |metric| {
830 insta::assert_json_snapshot!(
832 metric.nargs,
833 @r###"
834 {
835 "total_functions": 3.0,
836 "total_closures": 2.0,
837 "average_functions": 1.5,
838 "average_closures": 1.0,
839 "total": 5.0,
840 "average": 1.25,
841 "functions_min": 0.0,
842 "functions_max": 2.0,
843 "closures_min": 0.0,
844 "closures_max": 2.0
845 }"###
846 );
847 },
848 );
849 }
850
851 #[test]
852 fn rust_nested_functions() {
853 check_metrics::<RustParser>(
854 "fn f(a: i32, b: i32) -> i32 {
855 fn foo(a: i32) -> i32 {
856 return a;
857 }
858 let bar = |a: i32, b: i32| -> i32 { a + 1 };
859 let bar1 = |b: i32| -> i32 { b + 1 };
860 return bar(foo(a), a);
861 }",
862 "foo.rs",
863 |metric| {
864 insta::assert_json_snapshot!(
866 metric.nargs,
867 @r###"
868 {
869 "total_functions": 3.0,
870 "total_closures": 3.0,
871 "average_functions": 1.5,
872 "average_closures": 1.5,
873 "total": 6.0,
874 "average": 1.5,
875 "functions_min": 0.0,
876 "functions_max": 2.0,
877 "closures_min": 0.0,
878 "closures_max": 2.0
879 }"###
880 );
881 },
882 );
883 }
884
885 #[test]
886 fn cpp_nested_functions() {
887 check_metrics::<CppParser>(
888 "int f(int a, int b, int c) {
889 auto foo = [](int x) -> int { return x; };
890 auto bar = [](int x, int y) -> int { return x + y; };
891 return bar(foo(a), a);
892 }",
893 "foo.cpp",
894 |metric| {
895 insta::assert_json_snapshot!(
897 metric.nargs,
898 @r###"
899 {
900 "total_functions": 3.0,
901 "total_closures": 3.0,
902 "average_functions": 3.0,
903 "average_closures": 1.5,
904 "total": 6.0,
905 "average": 2.0,
906 "functions_min": 0.0,
907 "functions_max": 3.0,
908 "closures_min": 0.0,
909 "closures_max": 3.0
910 }"###
911 );
912 },
913 );
914 }
915
916 #[test]
917 fn javascript_nested_functions() {
918 check_metrics::<JavascriptParser>(
919 "function f(a, b) {
920 function foo(a, c) {
921 return a;
922 }
923 var bar = function (a, b) {return a + b};
924 function (a) {return a};
925 return bar(foo(a), a);
926 }",
927 "foo.js",
928 |metric| {
929 insta::assert_json_snapshot!(
931 metric.nargs,
932 @r###"
933 {
934 "total_functions": 6.0,
935 "total_closures": 1.0,
936 "average_functions": 2.0,
937 "average_closures": 1.0,
938 "total": 7.0,
939 "average": 1.75,
940 "functions_min": 0.0,
941 "functions_max": 2.0,
942 "closures_min": 0.0,
943 "closures_max": 1.0
944 }"###
945 );
946 },
947 );
948 }
949}