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