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#[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 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 #[inline(always)]
102 pub fn functions(&self) -> f64 {
103 self.functions as f64
105 }
106
107 #[inline(always)]
109 pub fn closures(&self) -> f64 {
110 self.closures as f64
111 }
112
113 #[inline(always)]
115 pub fn functions_sum(&self) -> f64 {
116 self.functions_sum as f64
118 }
119
120 #[inline(always)]
122 pub fn closures_sum(&self) -> f64 {
123 self.closures_sum as f64
124 }
125
126 #[inline(always)]
128 pub fn functions_average(&self) -> f64 {
129 self.functions_sum() / self.space_count as f64
130 }
131
132 #[inline(always)]
134 pub fn closures_average(&self) -> f64 {
135 self.closures_sum() / self.space_count as f64
136 }
137
138 #[inline(always)]
140 pub fn average(&self) -> f64 {
141 self.total() / self.space_count as f64
142 }
143
144 #[inline(always)]
146 pub fn functions_min(&self) -> f64 {
147 self.functions_min as f64
149 }
150
151 #[inline(always)]
153 pub fn closures_min(&self) -> f64 {
154 self.closures_min as f64
155 }
156 #[inline(always)]
158 pub fn functions_max(&self) -> f64 {
159 self.functions_max as f64
161 }
162
163 #[inline(always)]
165 pub fn closures_max(&self) -> f64 {
166 self.closures_max as f64
167 }
168 #[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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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 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}