Skip to main content

rust_code_analysis_code_split/metrics/
cognitive.rs

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// TODO: Find a way to increment the cognitive complexity value
12// for recursive code. For some kind of languages, such as C++, it is pretty
13// hard to detect, just parsing the code, if a determined function is recursive
14// because the call graph of a function is solved at runtime.
15// So a possible solution could be searching for a crate which implements
16// a light language interpreter, computing the call graph, and then detecting
17// if there are cycles. At this point, it is possible to figure out if a
18// function is recursive or not.
19
20/// The `Cognitive Complexity` metric.
21#[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    /// Merges a second `Cognitive Complexity` metric into the first one
75    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    /// Returns the `Cognitive Complexity` metric value
82    pub fn cognitive(&self) -> f64 {
83        self.structural as f64
84    }
85    /// Returns the `Cognitive Complexity` sum metric value
86    pub fn cognitive_sum(&self) -> f64 {
87        self.structural_sum as f64
88    }
89
90    /// Returns the `Cognitive Complexity` minimum metric value
91    pub fn cognitive_min(&self) -> f64 {
92        self.structural_min as f64
93    }
94    /// Returns the `Cognitive Complexity` maximum metric value
95    pub fn cognitive_max(&self) -> f64 {
96        self.structural_max as f64
97    }
98
99    /// Returns the `Cognitive Complexity` metric average value
100    ///
101    /// This value is computed dividing the `Cognitive Complexity` value
102    /// for the total number of functions/closures in a space.
103    ///
104    /// If there are no functions in a code, its value is `NAN`.
105    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                // The boolean operator is different from the previous one, so
168                // the counter is incremented.
169                structural + 1
170            } else {
171                // The boolean operator is equal to the previous one, so
172                // the counter is not incremented.
173                structural
174            }
175        } else {
176            // Save the first boolean operator in a sequence of
177            // logical operators and increment the counter.
178            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    // Increase depth function nesting if needed
215    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        // Get nesting of the parent
242        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                // No nesting increment for them because their cost has already
250                // been paid by the if construct
251                increment_by_one(stats);
252                // Reset the boolean sequence
253                stats.boolean_seq.reset();
254            }
255            ElseClause | FinallyClause => {
256                // No nesting increment for them because their cost has already
257                // been paid by the if construct
258                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                // Increase lambda nesting
290                lambda += 1;
291            }
292            FunctionDefinition => {
293                // Increase depth function nesting if needed
294                increment_function_depth::<language_python::Python>(
295                    &mut depth,
296                    node,
297                    FunctionDefinition,
298                );
299            }
300            _ => {}
301        }
302        // Add node to nesting map
303        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        //TODO: Implement macros
315        let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
316
317        match node.kind_id().into() {
318            IfExpression => {
319                // Check if a node is not an else-if
320                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 /*else-if also */ => {
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                // Increase depth function nesting if needed
346                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        //TODO: Implement macros
366        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 /* else-if also */ => {
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 /* else-if also */ => {
411                    increment_by_one(stats);
412                }
413                ExpressionStatement => {
414                    // Reset the boolean sequence
415                    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                    // Reset lambda nesting at function for JS
425                    nesting = 0;
426                    lambda = 0;
427                    // Increase depth function nesting if needed
428                    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 /* else-if also */ => {
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        // Boolean expressions containing `And` and `Or` operators were not
593        // considered in assignments
594        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        // Boolean expressions containing `And` and `Or` operators were not
617        // considered inside tuples
618        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        // Boolean expressions containing `And` and `Or` operators were not
641        // considered in `elif` statements
642        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        // Boolean expressions containing `And` and `Or` operators were not
668        // considered when there were more `elif` statements
669        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        // Only labeled break and continue statements are considered
1473        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                // 2 functions + 2 lambdas = 4
1640                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}