1use std::collections::HashMap;
2
3use serde::Serialize;
4use serde::ser::{SerializeStruct, Serializer};
5use std::fmt;
6
7use crate::checker::Checker;
8use crate::macros::implement_metric_trait;
9use crate::*;
10
11#[derive(Debug, Clone)]
22pub struct Stats {
23 structural: usize,
24 structural_sum: usize,
25 structural_min: usize,
26 structural_max: usize,
27 nesting: usize,
28 total_space_functions: usize,
29 boolean_seq: BoolSequence,
30}
31
32impl Default for Stats {
33 fn default() -> Self {
34 Self {
35 structural: 0,
36 structural_sum: 0,
37 structural_min: usize::MAX,
38 structural_max: 0,
39 nesting: 0,
40 total_space_functions: 1,
41 boolean_seq: BoolSequence::default(),
42 }
43 }
44}
45
46impl Serialize for Stats {
47 fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
48 where
49 S: Serializer,
50 {
51 let mut st = serializer.serialize_struct("cognitive", 4)?;
52 st.serialize_field("sum", &self.cognitive_sum())?;
53 st.serialize_field("average", &self.cognitive_average())?;
54 st.serialize_field("min", &self.cognitive_min())?;
55 st.serialize_field("max", &self.cognitive_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 "sum: {}, average: {}, min:{}, max: {}",
65 self.cognitive(),
66 self.cognitive_average(),
67 self.cognitive_min(),
68 self.cognitive_max()
69 )
70 }
71}
72
73impl Stats {
74 pub fn merge(&mut self, other: &Stats) {
76 self.structural_min = self.structural_min.min(other.structural_min);
77 self.structural_max = self.structural_max.max(other.structural_max);
78 self.structural_sum += other.structural_sum;
79 }
80
81 pub fn cognitive(&self) -> f64 {
83 self.structural as f64
84 }
85 pub fn cognitive_sum(&self) -> f64 {
87 self.structural_sum as f64
88 }
89
90 pub fn cognitive_min(&self) -> f64 {
92 self.structural_min as f64
93 }
94 pub fn cognitive_max(&self) -> f64 {
96 self.structural_max as f64
97 }
98
99 pub fn cognitive_average(&self) -> f64 {
106 self.cognitive_sum() / self.total_space_functions as f64
107 }
108 #[inline(always)]
109 pub(crate) fn compute_sum(&mut self) {
110 self.structural_sum += self.structural;
111 }
112 #[inline(always)]
113 pub(crate) fn compute_minmax(&mut self) {
114 self.structural_min = self.structural_min.min(self.structural);
115 self.structural_max = self.structural_max.max(self.structural);
116 self.compute_sum();
117 }
118
119 pub(crate) fn finalize(&mut self, total_space_functions: usize) {
120 self.total_space_functions = total_space_functions;
121 }
122}
123
124pub trait Cognitive
125where
126 Self: Checker,
127{
128 fn compute(
129 node: &Node,
130 stats: &mut Stats,
131 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
132 );
133}
134
135fn compute_booleans<T: std::cmp::PartialEq + std::convert::From<u16>>(
136 node: &Node,
137 stats: &mut Stats,
138 typs1: T,
139 typs2: T,
140) {
141 for child in node.children() {
142 if typs1 == child.kind_id().into() || typs2 == child.kind_id().into() {
143 stats.structural = stats
144 .boolean_seq
145 .eval_based_on_prev(child.kind_id(), stats.structural)
146 }
147 }
148}
149
150#[derive(Debug, Default, Clone)]
151struct BoolSequence {
152 boolean_op: Option<u16>,
153}
154
155impl BoolSequence {
156 fn reset(&mut self) {
157 self.boolean_op = None;
158 }
159
160 fn not_operator(&mut self, not_id: u16) {
161 self.boolean_op = Some(not_id);
162 }
163
164 fn eval_based_on_prev(&mut self, bool_id: u16, structural: usize) -> usize {
165 if let Some(prev) = self.boolean_op {
166 if prev != bool_id {
167 structural + 1
170 } else {
171 structural
174 }
175 } else {
176 self.boolean_op = Some(bool_id);
179 structural + 1
180 }
181 }
182}
183
184#[inline(always)]
185fn increment(stats: &mut Stats) {
186 stats.structural += stats.nesting + 1;
187}
188
189#[inline(always)]
190fn increment_by_one(stats: &mut Stats) {
191 stats.structural += 1;
192}
193
194fn get_nesting_from_map(
195 node: &Node,
196 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
197) -> (usize, usize, usize) {
198 if let Some(parent) = node.parent() {
199 if let Some(n) = nesting_map.get(&parent.id()) {
200 *n
201 } else {
202 (0, 0, 0)
203 }
204 } else {
205 (0, 0, 0)
206 }
207}
208
209fn increment_function_depth<T: std::cmp::PartialEq + std::convert::From<u16>>(
210 depth: &mut usize,
211 node: &Node,
212 stop: T,
213) {
214 let mut child = *node;
216 while let Some(parent) = child.parent() {
217 if stop == parent.kind_id().into() {
218 *depth += 1;
219 break;
220 }
221 child = parent;
222 }
223}
224
225#[inline(always)]
226fn increase_nesting(stats: &mut Stats, nesting: &mut usize, depth: usize, lambda: usize) {
227 stats.nesting = *nesting + depth + lambda;
228 increment(stats);
229 *nesting += 1;
230 stats.boolean_seq.reset();
231}
232
233impl Cognitive for PythonCode {
234 fn compute(
235 node: &Node,
236 stats: &mut Stats,
237 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
238 ) {
239 use Python::*;
240
241 let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
243
244 match node.kind_id().into() {
245 IfStatement | ForStatement | WhileStatement | ConditionalExpression => {
246 increase_nesting(stats, &mut nesting, depth, lambda);
247 }
248 ElifClause => {
249 increment_by_one(stats);
252 stats.boolean_seq.reset();
254 }
255 ElseClause | FinallyClause => {
256 increment_by_one(stats);
259 }
260 ExceptClause => {
261 nesting += 1;
262 increment(stats);
263 }
264 ExpressionList | ExpressionStatement | Tuple => {
265 stats.boolean_seq.reset();
266 }
267 NotOperator => {
268 stats.boolean_seq.not_operator(node.kind_id());
269 }
270 BooleanOperator => {
271 if node.count_specific_ancestors::<PythonParser>(
272 |node| node.kind_id() == BooleanOperator,
273 |node| node.kind_id() == Lambda,
274 ) == 0
275 {
276 stats.structural += node.count_specific_ancestors::<PythonParser>(
277 |node| node.kind_id() == Lambda,
278 |node| {
279 matches!(
280 node.kind_id().into(),
281 ExpressionList | IfStatement | ForStatement | WhileStatement
282 )
283 },
284 );
285 }
286 compute_booleans::<language_python::Python>(node, stats, And, Or);
287 }
288 Lambda => {
289 lambda += 1;
291 }
292 FunctionDefinition => {
293 increment_function_depth::<language_python::Python>(
295 &mut depth,
296 node,
297 FunctionDefinition,
298 );
299 }
300 _ => {}
301 }
302 nesting_map.insert(node.id(), (nesting, depth, lambda));
304 }
305}
306
307impl Cognitive for RustCode {
308 fn compute(
309 node: &Node,
310 stats: &mut Stats,
311 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
312 ) {
313 use Rust::*;
314 let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
316
317 match node.kind_id().into() {
318 IfExpression => {
319 if !Self::is_else_if(node) {
321 increase_nesting(stats,&mut nesting, depth, lambda);
322 }
323 }
324 ForExpression | WhileExpression | MatchExpression => {
325 increase_nesting(stats,&mut nesting, depth, lambda);
326 }
327 Else => {
328 increment_by_one(stats);
329 }
330 BreakExpression | ContinueExpression => {
331 if let Some(label_child) = node.child(1)
332 && let Label = label_child.kind_id().into()
333 {
334 increment_by_one(stats);
335 }
336 }
337 UnaryExpression => {
338 stats.boolean_seq.not_operator(node.kind_id());
339 }
340 BinaryExpression => {
341 compute_booleans::<language_rust::Rust>(node, stats, AMPAMP, PIPEPIPE);
342 }
343 FunctionItem => {
344 nesting = 0;
345 increment_function_depth::<language_rust::Rust>(&mut depth, node, FunctionItem);
347 }
348 ClosureExpression => {
349 lambda += 1;
350 }
351 _ => {}
352 }
353 nesting_map.insert(node.id(), (nesting, depth, lambda));
354 }
355}
356
357impl Cognitive for CppCode {
358 fn compute(
359 node: &Node,
360 stats: &mut Stats,
361 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
362 ) {
363 use Cpp::*;
364
365 let (mut nesting, depth, mut lambda) = get_nesting_from_map(node, nesting_map);
367
368 match node.kind_id().into() {
369 IfStatement => {
370 if !Self::is_else_if(node) {
371 increase_nesting(stats,&mut nesting, depth, lambda);
372 }
373 }
374 ForStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause => {
375 increase_nesting(stats,&mut nesting, depth, lambda);
376 }
377 GotoStatement | Else => {
378 increment_by_one(stats);
379 }
380 UnaryExpression2 => {
381 stats.boolean_seq.not_operator(node.kind_id());
382 }
383 BinaryExpression2 => {
384 compute_booleans::<language_cpp::Cpp>(node, stats, AMPAMP, PIPEPIPE);
385 }
386 LambdaExpression => {
387 lambda += 1;
388 }
389 _ => {}
390 }
391 nesting_map.insert(node.id(), (nesting, depth, lambda));
392 }
393}
394
395macro_rules! js_cognitive {
396 ($lang:ident) => {
397 fn compute(node: &Node, stats: &mut Stats, nesting_map: &mut HashMap<usize, (usize, usize, usize)>) {
398 use $lang::*;
399 let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
400
401 match node.kind_id().into() {
402 IfStatement => {
403 if !Self::is_else_if(&node) {
404 increase_nesting(stats,&mut nesting, depth, lambda);
405 }
406 }
407 ForStatement | ForInStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause | TernaryExpression => {
408 increase_nesting(stats,&mut nesting, depth, lambda);
409 }
410 Else => {
411 increment_by_one(stats);
412 }
413 ExpressionStatement => {
414 stats.boolean_seq.reset();
416 }
417 UnaryExpression => {
418 stats.boolean_seq.not_operator(node.kind_id());
419 }
420 BinaryExpression => {
421 compute_booleans::<$lang>(node, stats, AMPAMP, PIPEPIPE);
422 }
423 FunctionDeclaration => {
424 nesting = 0;
426 lambda = 0;
427 increment_function_depth::<$lang>(&mut depth, node, FunctionDeclaration);
429 }
430 ArrowFunction => {
431 lambda += 1;
432 }
433 _ => {}
434 }
435 nesting_map.insert(node.id(), (nesting, depth, lambda));
436 }
437 };
438}
439
440impl Cognitive for MozjsCode {
441 js_cognitive!(Mozjs);
442}
443
444impl Cognitive for JavascriptCode {
445 js_cognitive!(Javascript);
446}
447
448impl Cognitive for TypescriptCode {
449 js_cognitive!(Typescript);
450}
451
452impl Cognitive for TsxCode {
453 js_cognitive!(Tsx);
454}
455
456impl Cognitive for JavaCode {
457 fn compute(
458 node: &Node,
459 stats: &mut Stats,
460 nesting_map: &mut HashMap<usize, (usize, usize, usize)>,
461 ) {
462 use Java::*;
463
464 let (mut nesting, depth, mut lambda) = get_nesting_from_map(node, nesting_map);
465
466 match node.kind_id().into() {
467 IfStatement => {
468 if !Self::is_else_if(node) {
469 increase_nesting(stats,&mut nesting, depth, lambda);
470 }
471 }
472 ForStatement | WhileStatement | DoStatement | SwitchBlock | CatchClause => {
473 increase_nesting(stats,&mut nesting, depth, lambda);
474 }
475 Else => {
476 increment_by_one(stats);
477 }
478 UnaryExpression => {
479 stats.boolean_seq.not_operator(node.kind_id());
480 }
481 BinaryExpression => {
482 compute_booleans::<language_java::Java>(node, stats, AMPAMP, PIPEPIPE);
483 }
484 LambdaExpression => {
485 lambda += 1;
486 }
487 _ => {}
488 }
489 nesting_map.insert(node.id(), (nesting, depth, lambda));
490 }
491}
492
493implement_metric_trait!(Cognitive, PreprocCode, CcommentCode, KotlinCode);
494
495#[cfg(test)]
496mod tests {
497 use crate::tools::check_metrics;
498
499 use super::*;
500
501 #[test]
502 fn python_no_cognitive() {
503 check_metrics::<PythonParser>("a = 42", "foo.py", |metric| {
504 insta::assert_json_snapshot!(
505 metric.cognitive,
506 @r###"
507 {
508 "sum": 0.0,
509 "average": null,
510 "min": 0.0,
511 "max": 0.0
512 }"###
513 );
514 });
515 }
516
517 #[test]
518 fn rust_no_cognitive() {
519 check_metrics::<RustParser>("let a = 42;", "foo.rs", |metric| {
520 insta::assert_json_snapshot!(
521 metric.cognitive,
522 @r###"
523 {
524 "sum": 0.0,
525 "average": null,
526 "min": 0.0,
527 "max": 0.0
528 }"###
529 );
530 });
531 }
532
533 #[test]
534 fn c_no_cognitive() {
535 check_metrics::<CppParser>("int a = 42;", "foo.c", |metric| {
536 insta::assert_json_snapshot!(
537 metric.cognitive,
538 @r###"
539 {
540 "sum": 0.0,
541 "average": null,
542 "min": 0.0,
543 "max": 0.0
544 }"###
545 );
546 });
547 }
548
549 #[test]
550 fn mozjs_no_cognitive() {
551 check_metrics::<MozjsParser>("var a = 42;", "foo.js", |metric| {
552 insta::assert_json_snapshot!(
553 metric.cognitive,
554 @r###"
555 {
556 "sum": 0.0,
557 "average": null,
558 "min": 0.0,
559 "max": 0.0
560 }"###
561 );
562 });
563 }
564
565 #[test]
566 fn python_simple_function() {
567 check_metrics::<PythonParser>(
568 "def f(a, b):
569 if a and b: # +2 (+1 and)
570 return 1
571 if c and d: # +2 (+1 and)
572 return 1",
573 "foo.py",
574 |metric| {
575 insta::assert_json_snapshot!(
576 metric.cognitive,
577 @r#"
578 {
579 "sum": 2.0,
580 "average": 2.0,
581 "min": 0.0,
582 "max": 2.0
583 }
584 "#
585 );
586 },
587 );
588 }
589
590 #[test]
591 fn python_expression_statement() {
592 check_metrics::<PythonParser>(
595 "def f(a, b):
596 c = True and True",
597 "foo.py",
598 |metric| {
599 insta::assert_json_snapshot!(
600 metric.cognitive,
601 @r#"
602 {
603 "sum": 0.0,
604 "average": 0.0,
605 "min": 0.0,
606 "max": 0.0
607 }
608 "#
609 );
610 },
611 );
612 }
613
614 #[test]
615 fn python_tuple() {
616 check_metrics::<PythonParser>(
619 "def f(a, b):
620 return \"%s%s\" % (a and \"Get\" or \"Set\", b)",
621 "foo.py",
622 |metric| {
623 insta::assert_json_snapshot!(
624 metric.cognitive,
625 @r#"
626 {
627 "sum": 0.0,
628 "average": 0.0,
629 "min": 0.0,
630 "max": 0.0
631 }
632 "#
633 );
634 },
635 );
636 }
637
638 #[test]
639 fn python_elif_function() {
640 check_metrics::<PythonParser>(
643 "def f(a, b):
644 if a and b: # +2 (+1 and)
645 return 1
646 elif c and d: # +2 (+1 and)
647 return 1",
648 "foo.py",
649 |metric| {
650 insta::assert_json_snapshot!(
651 metric.cognitive,
652 @r#"
653 {
654 "sum": 2.0,
655 "average": 2.0,
656 "min": 0.0,
657 "max": 2.0
658 }
659 "#
660 );
661 },
662 );
663 }
664
665 #[test]
666 fn python_more_elifs_function() {
667 check_metrics::<PythonParser>(
670 "def f(a, b):
671 if a and b: # +2 (+1 and)
672 return 1
673 elif c and d: # +2 (+1 and)
674 return 1
675 elif e and f: # +2 (+1 and)
676 return 1",
677 "foo.py",
678 |metric| {
679 insta::assert_json_snapshot!(
680 metric.cognitive,
681 @r#"
682 {
683 "sum": 3.0,
684 "average": 3.0,
685 "min": 0.0,
686 "max": 3.0
687 }
688 "#
689 );
690 },
691 );
692 }
693
694 #[test]
695 fn rust_simple_function() {
696 check_metrics::<RustParser>(
697 "fn f() {
698 if a && b { // +2 (+1 &&)
699 println!(\"test\");
700 }
701 if c && d { // +2 (+1 &&)
702 println!(\"test\");
703 }
704 }",
705 "foo.rs",
706 |metric| {
707 insta::assert_json_snapshot!(
708 metric.cognitive,
709 @r###"
710 {
711 "sum": 4.0,
712 "average": 4.0,
713 "min": 0.0,
714 "max": 4.0
715 }"###
716 );
717 },
718 );
719 }
720
721 #[test]
722 fn c_simple_function() {
723 check_metrics::<CppParser>(
724 "void f() {
725 if (a && b) { // +2 (+1 &&)
726 printf(\"test\");
727 }
728 if (c && d) { // +2 (+1 &&)
729 printf(\"test\");
730 }
731 }",
732 "foo.c",
733 |metric| {
734 insta::assert_json_snapshot!(
735 metric.cognitive,
736 @r###"
737 {
738 "sum": 4.0,
739 "average": 4.0,
740 "min": 0.0,
741 "max": 4.0
742 }"###
743 );
744 },
745 );
746 }
747
748 #[test]
749 fn mozjs_simple_function() {
750 check_metrics::<MozjsParser>(
751 "function f() {
752 if (a && b) { // +2 (+1 &&)
753 window.print(\"test\");
754 }
755 if (c && d) { // +2 (+1 &&)
756 window.print(\"test\");
757 }
758 }",
759 "foo.js",
760 |metric| {
761 insta::assert_json_snapshot!(
762 metric.cognitive,
763 @r###"
764 {
765 "sum": 4.0,
766 "average": 4.0,
767 "min": 0.0,
768 "max": 4.0
769 }"###
770 );
771 },
772 );
773 }
774
775 #[test]
776 fn python_sequence_same_booleans() {
777 check_metrics::<PythonParser>(
778 "def f(a, b):
779 if a and b and True: # +2 (+1 sequence of and)
780 return 1",
781 "foo.py",
782 |metric| {
783 insta::assert_json_snapshot!(
784 metric.cognitive,
785 @r#"
786 {
787 "sum": 1.0,
788 "average": 1.0,
789 "min": 0.0,
790 "max": 1.0
791 }
792 "#
793 );
794 },
795 );
796 }
797
798 #[test]
799 fn rust_sequence_same_booleans() {
800 check_metrics::<RustParser>(
801 "fn f() {
802 if a && b && true { // +2 (+1 sequence of &&)
803 println!(\"test\");
804 }
805 }",
806 "foo.rs",
807 |metric| {
808 insta::assert_json_snapshot!(
809 metric.cognitive,
810 @r###"
811 {
812 "sum": 2.0,
813 "average": 2.0,
814 "min": 0.0,
815 "max": 2.0
816 }"###
817 );
818 },
819 );
820
821 check_metrics::<RustParser>(
822 "fn f() {
823 if a || b || c || d { // +2 (+1 sequence of ||)
824 println!(\"test\");
825 }
826 }",
827 "foo.rs",
828 |metric| {
829 insta::assert_json_snapshot!(
830 metric.cognitive,
831 @r###"
832 {
833 "sum": 2.0,
834 "average": 2.0,
835 "min": 0.0,
836 "max": 2.0
837 }"###
838 );
839 },
840 );
841 }
842
843 #[test]
844 fn c_sequence_same_booleans() {
845 check_metrics::<CppParser>(
846 "void f() {
847 if (a && b && 1 == 1) { // +2 (+1 sequence of &&)
848 printf(\"test\");
849 }
850 }",
851 "foo.c",
852 |metric| {
853 insta::assert_json_snapshot!(
854 metric.cognitive,
855 @r###"
856 {
857 "sum": 2.0,
858 "average": 2.0,
859 "min": 0.0,
860 "max": 2.0
861 }"###
862 );
863 },
864 );
865
866 check_metrics::<CppParser>(
867 "void f() {
868 if (a || b || c || d) { // +2 (+1 sequence of ||)
869 printf(\"test\");
870 }
871 }",
872 "foo.c",
873 |metric| {
874 insta::assert_json_snapshot!(
875 metric.cognitive,
876 @r###"
877 {
878 "sum": 2.0,
879 "average": 2.0,
880 "min": 0.0,
881 "max": 2.0
882 }"###
883 );
884 },
885 );
886 }
887
888 #[test]
889 fn mozjs_sequence_same_booleans() {
890 check_metrics::<MozjsParser>(
891 "function f() {
892 if (a && b && 1 == 1) { // +2 (+1 sequence of &&)
893 window.print(\"test\");
894 }
895 }",
896 "foo.js",
897 |metric| {
898 insta::assert_json_snapshot!(
899 metric.cognitive,
900 @r###"
901 {
902 "sum": 2.0,
903 "average": 2.0,
904 "min": 0.0,
905 "max": 2.0
906 }"###
907 );
908 },
909 );
910
911 check_metrics::<MozjsParser>(
912 "function f() {
913 if (a || b || c || d) { // +2 (+1 sequence of ||)
914 window.print(\"test\");
915 }
916 }",
917 "foo.js",
918 |metric| {
919 insta::assert_json_snapshot!(
920 metric.cognitive,
921 @r###"
922 {
923 "sum": 2.0,
924 "average": 2.0,
925 "min": 0.0,
926 "max": 2.0
927 }"###
928 );
929 },
930 );
931 }
932
933 #[test]
934 fn rust_not_booleans() {
935 check_metrics::<RustParser>(
936 "fn f() {
937 if !a && !b { // +2 (+1 &&)
938 println!(\"test\");
939 }
940 }",
941 "foo.rs",
942 |metric| {
943 insta::assert_json_snapshot!(
944 metric.cognitive,
945 @r###"
946 {
947 "sum": 2.0,
948 "average": 2.0,
949 "min": 0.0,
950 "max": 2.0
951 }"###
952 );
953 },
954 );
955
956 check_metrics::<RustParser>(
957 "fn f() {
958 if a && !(b && c) { // +3 (+1 &&, +1 &&)
959 println!(\"test\");
960 }
961 }",
962 "foo.rs",
963 |metric| {
964 insta::assert_json_snapshot!(
965 metric.cognitive,
966 @r###"
967 {
968 "sum": 3.0,
969 "average": 3.0,
970 "min": 0.0,
971 "max": 3.0
972 }"###
973 );
974 },
975 );
976
977 check_metrics::<RustParser>(
978 "fn f() {
979 if !(a || b) && !(c || d) { // +4 (+1 ||, +1 &&, +1 ||)
980 println!(\"test\");
981 }
982 }",
983 "foo.rs",
984 |metric| {
985 insta::assert_json_snapshot!(
986 metric.cognitive,
987 @r###"
988 {
989 "sum": 4.0,
990 "average": 4.0,
991 "min": 0.0,
992 "max": 4.0
993 }"###
994 );
995 },
996 );
997 }
998
999 #[test]
1000 fn c_not_booleans() {
1001 check_metrics::<CppParser>(
1002 "void f() {
1003 if (a && !(b && c)) { // +3 (+1 &&, +1 &&)
1004 printf(\"test\");
1005 }
1006 }",
1007 "foo.c",
1008 |metric| {
1009 insta::assert_json_snapshot!(
1010 metric.cognitive,
1011 @r###"
1012 {
1013 "sum": 3.0,
1014 "average": 3.0,
1015 "min": 0.0,
1016 "max": 3.0
1017 }"###
1018 );
1019 },
1020 );
1021
1022 check_metrics::<CppParser>(
1023 "void f() {
1024 if (!(a || b) && !(c || d)) { // +4 (+1 ||, +1 &&, +1 ||)
1025 printf(\"test\");
1026 }
1027 }",
1028 "foo.c",
1029 |metric| {
1030 insta::assert_json_snapshot!(
1031 metric.cognitive,
1032 @r###"
1033 {
1034 "sum": 4.0,
1035 "average": 4.0,
1036 "min": 0.0,
1037 "max": 4.0
1038 }"###
1039 );
1040 },
1041 );
1042 }
1043
1044 #[test]
1045 fn mozjs_not_booleans() {
1046 check_metrics::<MozjsParser>(
1047 "function f() {
1048 if (a && !(b && c)) { // +3 (+1 &&, +1 &&)
1049 window.print(\"test\");
1050 }
1051 }",
1052 "foo.js",
1053 |metric| {
1054 insta::assert_json_snapshot!(
1055 metric.cognitive,
1056 @r###"
1057 {
1058 "sum": 3.0,
1059 "average": 3.0,
1060 "min": 0.0,
1061 "max": 3.0
1062 }"###
1063 );
1064 },
1065 );
1066
1067 check_metrics::<MozjsParser>(
1068 "function f() {
1069 if (!(a || b) && !(c || d)) { // +4 (+1 ||, +1 &&, +1 ||)
1070 window.print(\"test\");
1071 }
1072 }",
1073 "foo.js",
1074 |metric| {
1075 insta::assert_json_snapshot!(
1076 metric.cognitive,
1077 @r###"
1078 {
1079 "sum": 4.0,
1080 "average": 4.0,
1081 "min": 0.0,
1082 "max": 4.0
1083 }"###
1084 );
1085 },
1086 );
1087 }
1088
1089 #[test]
1090 fn python_sequence_different_booleans() {
1091 check_metrics::<PythonParser>(
1092 "def f(a, b):
1093 if a and b or True: # +3 (+1 and, +1 or)
1094 return 1",
1095 "foo.py",
1096 |metric| {
1097 insta::assert_json_snapshot!(
1098 metric.cognitive,
1099 @r#"
1100 {
1101 "sum": 1.0,
1102 "average": 1.0,
1103 "min": 0.0,
1104 "max": 1.0
1105 }
1106 "#
1107 );
1108 },
1109 );
1110 }
1111
1112 #[test]
1113 fn rust_sequence_different_booleans() {
1114 check_metrics::<RustParser>(
1115 "fn f() {
1116 if a && b || true { // +3 (+1 &&, +1 ||)
1117 println!(\"test\");
1118 }
1119 }",
1120 "foo.rs",
1121 |metric| {
1122 insta::assert_json_snapshot!(
1123 metric.cognitive,
1124 @r###"
1125 {
1126 "sum": 3.0,
1127 "average": 3.0,
1128 "min": 0.0,
1129 "max": 3.0
1130 }"###
1131 );
1132 },
1133 );
1134 }
1135
1136 #[test]
1137 fn c_sequence_different_booleans() {
1138 check_metrics::<CppParser>(
1139 "void f() {
1140 if (a && b || 1 == 1) { // +3 (+1 &&, +1 ||)
1141 printf(\"test\");
1142 }
1143 }",
1144 "foo.c",
1145 |metric| {
1146 insta::assert_json_snapshot!(
1147 metric.cognitive,
1148 @r###"
1149 {
1150 "sum": 3.0,
1151 "average": 3.0,
1152 "min": 0.0,
1153 "max": 3.0
1154 }"###
1155 );
1156 },
1157 );
1158 }
1159
1160 #[test]
1161 fn mozjs_sequence_different_booleans() {
1162 check_metrics::<MozjsParser>(
1163 "function f() {
1164 if (a && b || 1 == 1) { // +3 (+1 &&, +1 ||)
1165 window.print(\"test\");
1166 }
1167 }",
1168 "foo.js",
1169 |metric| {
1170 insta::assert_json_snapshot!(
1171 metric.cognitive,
1172 @r###"
1173 {
1174 "sum": 3.0,
1175 "average": 3.0,
1176 "min": 0.0,
1177 "max": 3.0
1178 }"###
1179 );
1180 },
1181 );
1182 }
1183
1184 #[test]
1185 fn python_formatted_sequence_different_booleans() {
1186 check_metrics::<PythonParser>(
1187 "def f(a, b):
1188 if ( # +1
1189 a and b and # +1
1190 (c or d) # +1
1191 ):
1192 return 1",
1193 "foo.py",
1194 |metric| {
1195 insta::assert_json_snapshot!(
1196 metric.cognitive,
1197 @r#"
1198 {
1199 "sum": 1.0,
1200 "average": 1.0,
1201 "min": 0.0,
1202 "max": 1.0
1203 }
1204 "#
1205 );
1206 },
1207 );
1208 }
1209
1210 #[test]
1211 fn python_1_level_nesting() {
1212 check_metrics::<PythonParser>(
1213 "def f(a, b):
1214 if a: # +1
1215 for i in range(b): # +2
1216 return 1",
1217 "foo.py",
1218 |metric| {
1219 insta::assert_json_snapshot!(
1220 metric.cognitive,
1221 @r###"
1222 {
1223 "sum": 3.0,
1224 "average": 3.0,
1225 "min": 0.0,
1226 "max": 3.0
1227 }"###
1228 );
1229 },
1230 );
1231 }
1232
1233 #[test]
1234 fn rust_1_level_nesting() {
1235 check_metrics::<RustParser>(
1236 "fn f() {
1237 if true { // +1
1238 if true { // +2 (nesting = 1)
1239 println!(\"test\");
1240 } else if 1 == 1 { // +1
1241 if true { // +3 (nesting = 2)
1242 println!(\"test\");
1243 }
1244 } else { // +1
1245 if true { // +3 (nesting = 2)
1246 println!(\"test\");
1247 }
1248 }
1249 }
1250 }",
1251 "foo.rs",
1252 |metric| {
1253 insta::assert_json_snapshot!(
1254 metric.cognitive,
1255 @r###"
1256 {
1257 "sum": 11.0,
1258 "average": 11.0,
1259 "min": 0.0,
1260 "max": 11.0
1261 }"###
1262 );
1263 },
1264 );
1265
1266 check_metrics::<RustParser>(
1267 "fn f() {
1268 if true { // +1
1269 match true { // +2 (nesting = 1)
1270 true => println!(\"test\"),
1271 false => println!(\"test\"),
1272 }
1273 }
1274 }",
1275 "foo.rs",
1276 |metric| {
1277 insta::assert_json_snapshot!(
1278 metric.cognitive,
1279 @r###"
1280 {
1281 "sum": 3.0,
1282 "average": 3.0,
1283 "min": 0.0,
1284 "max": 3.0
1285 }"###
1286 );
1287 },
1288 );
1289 }
1290
1291 #[test]
1292 fn c_1_level_nesting() {
1293 check_metrics::<CppParser>(
1294 "void f() {
1295 if (1 == 1) { // +1
1296 if (1 == 1) { // +2 (nesting = 1)
1297 printf(\"test\");
1298 } else if (1 == 1) { // +1
1299 if (1 == 1) { // +3 (nesting = 2)
1300 printf(\"test\");
1301 }
1302 } else { // +1
1303 if (1 == 1) { // +3 (nesting = 2)
1304 printf(\"test\");
1305 }
1306 }
1307 }
1308 }",
1309 "foo.c",
1310 |metric| {
1311 insta::assert_json_snapshot!(
1312 metric.cognitive,
1313 @r###"
1314 {
1315 "sum": 11.0,
1316 "average": 11.0,
1317 "min": 0.0,
1318 "max": 11.0
1319 }"###
1320 );
1321 },
1322 );
1323 }
1324
1325 #[test]
1326 fn mozjs_1_level_nesting() {
1327 check_metrics::<MozjsParser>(
1328 "function f() {
1329 if (1 == 1) { // +1
1330 if (1 == 1) { // +2 (nesting = 1)
1331 window.print(\"test\");
1332 } else if (1 == 1) { // +1
1333 if (1 == 1) { // +3 (nesting = 2)
1334 window.print(\"test\");
1335 }
1336 } else { // +1
1337 if (1 == 1) { // +3 (nesting = 2)
1338 window.print(\"test\");
1339 }
1340 }
1341 }
1342 }",
1343 "foo.js",
1344 |metric| {
1345 insta::assert_json_snapshot!(
1346 metric.cognitive,
1347 @r###"
1348 {
1349 "sum": 11.0,
1350 "average": 11.0,
1351 "min": 0.0,
1352 "max": 11.0
1353 }"###
1354 );
1355 },
1356 );
1357 }
1358
1359 #[test]
1360 fn python_2_level_nesting() {
1361 check_metrics::<PythonParser>(
1362 "def f(a, b):
1363 if a: # +1
1364 for i in range(b): # +2
1365 if b: # +3
1366 return 1",
1367 "foo.py",
1368 |metric| {
1369 insta::assert_json_snapshot!(
1370 metric.cognitive,
1371 @r###"
1372 {
1373 "sum": 6.0,
1374 "average": 6.0,
1375 "min": 0.0,
1376 "max": 6.0
1377 }"###
1378 );
1379 },
1380 );
1381 }
1382
1383 #[test]
1384 fn rust_2_level_nesting() {
1385 check_metrics::<RustParser>(
1386 "fn f() {
1387 if true { // +1
1388 for i in 0..4 { // +2 (nesting = 1)
1389 match true { // +3 (nesting = 2)
1390 true => println!(\"test\"),
1391 false => println!(\"test\"),
1392 }
1393 }
1394 }
1395 }",
1396 "foo.rs",
1397 |metric| {
1398 insta::assert_json_snapshot!(
1399 metric.cognitive,
1400 @r###"
1401 {
1402 "sum": 6.0,
1403 "average": 6.0,
1404 "min": 0.0,
1405 "max": 6.0
1406 }"###
1407 );
1408 },
1409 );
1410 }
1411
1412 #[test]
1413 fn python_try_construct() {
1414 check_metrics::<PythonParser>(
1415 "def f(a, b):
1416 try:
1417 for foo in bar: # +1
1418 return a
1419 except Exception: # +1
1420 if a < 0: # +2
1421 return a",
1422 "foo.py",
1423 |metric| {
1424 insta::assert_json_snapshot!(
1425 metric.cognitive,
1426 @r###"
1427 {
1428 "sum": 4.0,
1429 "average": 4.0,
1430 "min": 0.0,
1431 "max": 4.0
1432 }"###
1433 );
1434 },
1435 );
1436 }
1437
1438 #[test]
1439 fn mozjs_try_construct() {
1440 check_metrics::<MozjsParser>(
1441 "function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
1442 for (const collector of this.collectors) {
1443 try {
1444 collector._onChannelRedirect(oldChannel, newChannel, flags);
1445 } catch (ex) {
1446 console.error(
1447 \"StackTraceCollector.onChannelRedirect threw an exception\",
1448 ex
1449 );
1450 }
1451 }
1452 callback.onRedirectVerifyCallback(Cr.NS_OK);
1453 }",
1454 "foo.js",
1455 |metric| {
1456 insta::assert_json_snapshot!(
1457 metric.cognitive,
1458 @r###"
1459 {
1460 "sum": 3.0,
1461 "average": 3.0,
1462 "min": 0.0,
1463 "max": 3.0
1464 }"###
1465 );
1466 },
1467 );
1468 }
1469
1470 #[test]
1471 fn rust_break_continue() {
1472 check_metrics::<RustParser>(
1474 "fn f() {
1475 'tens: for ten in 0..3 { // +1
1476 '_units: for unit in 0..=9 { // +2 (nesting = 1)
1477 if unit % 2 == 0 { // +3 (nesting = 2)
1478 continue;
1479 } else if unit == 5 { // +1
1480 continue 'tens; // +1
1481 } else if unit == 6 { // +1
1482 break;
1483 } else { // +1
1484 break 'tens; // +1
1485 }
1486 }
1487 }
1488 }",
1489 "foo.rs",
1490 |metric| {
1491 insta::assert_json_snapshot!(
1492 metric.cognitive,
1493 @r###"
1494 {
1495 "sum": 11.0,
1496 "average": 11.0,
1497 "min": 0.0,
1498 "max": 11.0
1499 }"###
1500 );
1501 },
1502 );
1503 }
1504
1505 #[test]
1506 fn c_goto() {
1507 check_metrics::<CppParser>(
1508 "void f() {
1509 OUT: for (int i = 1; i <= max; ++i) { // +1
1510 for (int j = 2; j < i; ++j) { // +2 (nesting = 1)
1511 if (i % j == 0) { // +3 (nesting = 2)
1512 goto OUT; // +1
1513 }
1514 }
1515 }
1516 }",
1517 "foo.c",
1518 |metric| {
1519 insta::assert_json_snapshot!(
1520 metric.cognitive,
1521 @r###"
1522 {
1523 "sum": 7.0,
1524 "average": 7.0,
1525 "min": 0.0,
1526 "max": 7.0
1527 }"###
1528 );
1529 },
1530 );
1531 }
1532
1533 #[test]
1534 fn c_switch() {
1535 check_metrics::<CppParser>(
1536 "void f() {
1537 switch (1) { // +1
1538 case 1:
1539 printf(\"one\");
1540 break;
1541 case 2:
1542 printf(\"two\");
1543 break;
1544 case 3:
1545 printf(\"three\");
1546 break;
1547 default:
1548 printf(\"all\");
1549 break;
1550 }
1551 }",
1552 "foo.c",
1553 |metric| {
1554 insta::assert_json_snapshot!(
1555 metric.cognitive,
1556 @r###"
1557 {
1558 "sum": 1.0,
1559 "average": 1.0,
1560 "min": 0.0,
1561 "max": 1.0
1562 }"###
1563 );
1564 },
1565 );
1566 }
1567
1568 #[test]
1569 fn mozjs_switch() {
1570 check_metrics::<MozjsParser>(
1571 "function f() {
1572 switch (1) { // +1
1573 case 1:
1574 window.print(\"one\");
1575 break;
1576 case 2:
1577 window.print(\"two\");
1578 break;
1579 case 3:
1580 window.print(\"three\");
1581 break;
1582 default:
1583 window.print(\"all\");
1584 break;
1585 }
1586 }",
1587 "foo.js",
1588 |metric| {
1589 insta::assert_json_snapshot!(
1590 metric.cognitive,
1591 @r###"
1592 {
1593 "sum": 1.0,
1594 "average": 1.0,
1595 "min": 0.0,
1596 "max": 1.0
1597 }"###
1598 );
1599 },
1600 );
1601 }
1602
1603 #[test]
1604 fn python_ternary_operator() {
1605 check_metrics::<PythonParser>(
1606 "def f(a, b):
1607 if a % 2: # +1
1608 return 'c' if a else 'd' # +2
1609 return 'a' if a else 'b' # +1",
1610 "foo.py",
1611 |metric| {
1612 insta::assert_json_snapshot!(
1613 metric.cognitive,
1614 @r#"
1615 {
1616 "sum": 1.0,
1617 "average": 1.0,
1618 "min": 0.0,
1619 "max": 1.0
1620 }
1621 "#
1622 );
1623 },
1624 );
1625 }
1626
1627 #[test]
1628 fn python_nested_functions_lambdas() {
1629 check_metrics::<PythonParser>(
1630 "def f(a, b):
1631 def foo(a):
1632 if a: # +2 (+1 nesting)
1633 return 1
1634 # +3 (+1 for boolean sequence +2 for lambda nesting)
1635 bar = lambda a: lambda b: b or True or True
1636 return bar(foo(a))(a)",
1637 "foo.py",
1638 |metric| {
1639 insta::assert_json_snapshot!(
1641 metric.cognitive,
1642 @r#"
1643 {
1644 "sum": 1.0,
1645 "average": 0.5,
1646 "min": 0.0,
1647 "max": 1.0
1648 }
1649 "#
1650 );
1651 },
1652 );
1653 }
1654
1655 #[test]
1656 fn python_real_function() {
1657 check_metrics::<PythonParser>(
1658 "def process_raw_constant(constant, min_word_length):
1659 processed_words = []
1660 raw_camelcase_words = []
1661 for raw_word in re.findall(r'[a-z]+', constant): # +1
1662 word = raw_word.strip()
1663 if ( # +2 (+1 if and +1 nesting)
1664 len(word) >= min_word_length
1665 and not (word.startswith('-') or word.endswith('-')) # +2 operators
1666 ):
1667 if is_camel_case_word(word): # +3 (+1 if and +2 nesting)
1668 raw_camelcase_words.append(word)
1669 else: # +1 else
1670 processed_words.append(word.lower())
1671 return processed_words, raw_camelcase_words",
1672 "foo.py",
1673 |metric| {
1674 insta::assert_json_snapshot!(
1675 metric.cognitive,
1676 @r#"
1677 {
1678 "sum": 7.0,
1679 "average": 7.0,
1680 "min": 0.0,
1681 "max": 7.0
1682 }
1683 "#
1684 );
1685 },
1686 );
1687 }
1688
1689 #[test]
1690 fn rust_if_let_else_if_else() {
1691 check_metrics::<RustParser>(
1692 "pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String {
1693 debugln!(\"usage::create_usage_no_title;\");
1694 if let Some(u) = p.meta.usage_str { // +1
1695 String::from(&*u)
1696 } else if used.is_empty() { // +1
1697 create_help_usage(p, true)
1698 } else { // +1
1699 create_smart_usage(p, used)
1700 }
1701 }",
1702 "foo.rs",
1703 |metric| {
1704 insta::assert_json_snapshot!(
1705 metric.cognitive,
1706 @r###"
1707 {
1708 "sum": 3.0,
1709 "average": 3.0,
1710 "min": 0.0,
1711 "max": 3.0
1712 }"###
1713 );
1714 },
1715 );
1716 }
1717
1718 #[test]
1719 fn typescript_if_else_if_else() {
1720 check_metrics::<TypescriptParser>(
1721 "function foo() {
1722 if (this._closed) return Promise.resolve(); // +1
1723 if (this._tempDirectory) { // +1
1724 this.kill();
1725 } else if (this.connection) { // +1
1726 this.kill();
1727 } else { // +1
1728 throw new Error(`Error`);
1729 }
1730 helper.removeEventListeners(this._listeners);
1731 return this._processClosing;
1732 }",
1733 "foo.ts",
1734 |metric| {
1735 insta::assert_json_snapshot!(
1736 metric.cognitive,
1737 @r###"
1738 {
1739 "sum": 4.0,
1740 "average": 4.0,
1741 "min": 0.0,
1742 "max": 4.0
1743 }"###
1744 );
1745 },
1746 );
1747 }
1748
1749 #[test]
1750 fn java_no_cognitive() {
1751 check_metrics::<JavaParser>("int a = 42;", "foo.java", |metric| {
1752 insta::assert_json_snapshot!(
1753 metric.cognitive,
1754 @r###"
1755 {
1756 "sum": 0.0,
1757 "average": null,
1758 "min": 0.0,
1759 "max": 0.0
1760 }
1761 "###
1762 );
1763 });
1764 }
1765
1766 #[test]
1767 fn java_single_branch_function() {
1768 check_metrics::<JavaParser>(
1769 "class X {
1770 public static void print(boolean a){
1771 if(a){ // +1
1772 System.out.println(\"test1\");
1773 }
1774 }
1775 }",
1776 "foo.java",
1777 |metric| {
1778 insta::assert_json_snapshot!(
1779 metric.cognitive,
1780 @r###"
1781 {
1782 "sum": 1.0,
1783 "average": 1.0,
1784 "min": 0.0,
1785 "max": 1.0
1786 }
1787 "###
1788 );
1789 },
1790 );
1791 }
1792
1793 #[test]
1794 fn java_multiple_branch_function() {
1795 check_metrics::<JavaParser>(
1796 "class X {
1797 public static void print(boolean a, boolean b){
1798 if(a){ // +1
1799 System.out.println(\"test1\");
1800 }
1801 if(b){ // +1
1802 System.out.println(\"test2\");
1803 }
1804 else { // +1
1805 System.out.println(\"test3\");
1806 }
1807 }
1808 }",
1809 "foo.java",
1810 |metric| {
1811 insta::assert_json_snapshot!(
1812 metric.cognitive,
1813 @r###"
1814 {
1815 "sum": 3.0,
1816 "average": 3.0,
1817 "min": 0.0,
1818 "max": 3.0
1819 }
1820 "###
1821 );
1822 },
1823 );
1824 }
1825
1826 #[test]
1827 fn java_compound_conditions() {
1828 check_metrics::<JavaParser>(
1829 "class X {
1830 public static void print(boolean a, boolean b, boolean c, boolean d){
1831 if(a && b){ // +2 (+1 &&)
1832 System.out.println(\"test1\");
1833 }
1834 if(c && d){ // +2 (+1 &&)
1835 System.out.println(\"test2\");
1836 }
1837 }
1838 }",
1839 "foo.java",
1840 |metric| {
1841 insta::assert_json_snapshot!(
1842 metric.cognitive,
1843 @r###"
1844 {
1845 "sum": 4.0,
1846 "average": 4.0,
1847 "min": 0.0,
1848 "max": 4.0
1849 }"###
1850 );
1851 },
1852 );
1853 }
1854
1855 #[test]
1856 fn java_switch_statement() {
1857 check_metrics::<JavaParser>(
1858 "class X {
1859 public static void print(boolean a, boolean b, boolean c, boolean d){
1860 switch(expr){ //+1
1861 case 1:
1862 System.out.println(\"test1\");
1863 break;
1864 case 2:
1865 System.out.println(\"test2\");
1866 break;
1867 default:
1868 System.out.println(\"test\");
1869 }
1870 }
1871 }",
1872 "foo.java",
1873 |metric| {
1874 insta::assert_json_snapshot!(
1875 metric.cognitive,
1876 @r###"
1877 {
1878 "sum": 1.0,
1879 "average": 1.0,
1880 "min": 0.0,
1881 "max": 1.0
1882 }"###
1883 );
1884 },
1885 );
1886 }
1887
1888 #[test]
1889 fn java_switch_expression() {
1890 check_metrics::<JavaParser>(
1891 "class X {
1892 public static void print(boolean a, boolean b, boolean c, boolean d){
1893 switch(expr){ // +1
1894 case 1 -> System.out.println(\"test1\");
1895 case 2 -> System.out.println(\"test2\");
1896 default -> System.out.println(\"test\");
1897 }
1898 }
1899 }",
1900 "foo.java",
1901 |metric| {
1902 insta::assert_json_snapshot!(
1903 metric.cognitive,
1904 @r###"
1905 {
1906 "sum": 1.0,
1907 "average": 1.0,
1908 "min": 0.0,
1909 "max": 1.0
1910 }"###
1911 );
1912 },
1913 );
1914 }
1915
1916 #[test]
1917 fn java_not_booleans() {
1918 check_metrics::<JavaParser>(
1919 "class X {
1920 public static void print(boolean a, boolean b, boolean c, boolean d){
1921 if (a && !(b && c)) { // +3 (+1 &&, +1 &&)
1922 printf(\"test\");
1923 }
1924 }
1925 }",
1926 "foo.java",
1927 |metric| {
1928 insta::assert_json_snapshot!(
1929 metric.cognitive,
1930 @r###"
1931 {
1932 "sum": 3.0,
1933 "average": 3.0,
1934 "min": 0.0,
1935 "max": 3.0
1936 }"###
1937 );
1938 },
1939 );
1940 }
1941}