rust_code_analysis/metrics/
cognitive.rs

1use fxhash::FxHashMap;
2use serde::ser::{SerializeStruct, Serializer};
3use serde::Serialize;
4use std::fmt;
5
6use crate::checker::Checker;
7use crate::*;
8
9// TODO: Find a way to increment the cognitive complexity value
10// for recursive code. For some kind of langauges, such as C++, it is pretty
11// hard to detect, just parsing the code, if a determined function is recursive
12// because the call graph of a function is solved at runtime.
13// So a possible solution could be searching for a crate which implements
14// a light language interpreter, computing the call graph, and then detecting
15// if there are cycles. At this point, it is possible to figure out if a
16// function is recursive or not.
17
18/// The `Cognitive Complexity` metric.
19#[derive(Debug, Clone)]
20pub struct Stats {
21    structural: usize,
22    structural_sum: usize,
23    structural_min: usize,
24    structural_max: usize,
25    nesting: usize,
26    total_space_functions: usize,
27    boolean_seq: BoolSequence,
28}
29
30impl Default for Stats {
31    fn default() -> Self {
32        Self {
33            structural: 0,
34            structural_sum: 0,
35            structural_min: usize::MAX,
36            structural_max: 0,
37            nesting: 0,
38            total_space_functions: 1,
39            boolean_seq: BoolSequence::default(),
40        }
41    }
42}
43
44impl Serialize for Stats {
45    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
46    where
47        S: Serializer,
48    {
49        let mut st = serializer.serialize_struct("cognitive", 4)?;
50        st.serialize_field("sum", &self.cognitive_sum())?;
51        st.serialize_field("average", &self.cognitive_average())?;
52        st.serialize_field("min", &self.cognitive_min())?;
53        st.serialize_field("max", &self.cognitive_max())?;
54        st.end()
55    }
56}
57
58impl fmt::Display for Stats {
59    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
60        write!(
61            f,
62            "sum: {}, average: {}, min:{}, max: {}",
63            self.cognitive(),
64            self.cognitive_average(),
65            self.cognitive_min(),
66            self.cognitive_max()
67        )
68    }
69}
70
71impl Stats {
72    /// Merges a second `Cognitive Complexity` metric into the first one
73    pub fn merge(&mut self, other: &Stats) {
74        self.structural_min = self.structural_min.min(other.structural_min);
75        self.structural_max = self.structural_max.max(other.structural_max);
76        self.structural_sum += other.structural_sum;
77    }
78
79    /// Returns the `Cognitive Complexity` metric value
80    pub fn cognitive(&self) -> f64 {
81        self.structural as f64
82    }
83    /// Returns the `Cognitive Complexity` sum metric value
84    pub fn cognitive_sum(&self) -> f64 {
85        self.structural_sum as f64
86    }
87
88    /// Returns the `Cognitive Complexity` minimum metric value
89    pub fn cognitive_min(&self) -> f64 {
90        self.structural_min as f64
91    }
92    /// Returns the `Cognitive Complexity` maximum metric value
93    pub fn cognitive_max(&self) -> f64 {
94        self.structural_max as f64
95    }
96
97    /// Returns the `Cognitive Complexity` metric average value
98    ///
99    /// This value is computed dividing the `Cognitive Complexity` value
100    /// for the total number of functions/closures in a space.
101    ///
102    /// If there are no functions in a code, its value is `NAN`.
103    pub fn cognitive_average(&self) -> f64 {
104        self.cognitive_sum() / self.total_space_functions as f64
105    }
106    #[inline(always)]
107    pub(crate) fn compute_sum(&mut self) {
108        self.structural_sum += self.structural;
109    }
110    #[inline(always)]
111    pub(crate) fn compute_minmax(&mut self) {
112        self.structural_min = self.structural_min.min(self.structural);
113        self.structural_max = self.structural_max.max(self.structural);
114        self.compute_sum();
115    }
116
117    pub(crate) fn finalize(&mut self, total_space_functions: usize) {
118        self.total_space_functions = total_space_functions;
119    }
120}
121
122#[doc(hidden)]
123pub trait Cognitive
124where
125    Self: Checker,
126{
127    fn compute(
128        _node: &Node,
129        _stats: &mut Stats,
130        _nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
131    ) {
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    let mut cursor = node.object().walk();
142    for child in node.object().children(&mut cursor) {
143        if typs1 == child.kind_id().into() || typs2 == child.kind_id().into() {
144            stats.structural = stats
145                .boolean_seq
146                .eval_based_on_prev(child.kind_id(), stats.structural)
147        }
148    }
149}
150
151#[derive(Debug, Default, Clone)]
152struct BoolSequence {
153    boolean_op: Option<u16>,
154}
155
156impl BoolSequence {
157    fn reset(&mut self) {
158        self.boolean_op = None;
159    }
160
161    fn not_operator(&mut self, not_id: u16) {
162        self.boolean_op = Some(not_id);
163    }
164
165    fn eval_based_on_prev(&mut self, bool_id: u16, structural: usize) -> usize {
166        if let Some(prev) = self.boolean_op {
167            if prev != bool_id {
168                // The boolean operator is different from the previous one, so
169                // the counter is incremented.
170                structural + 1
171            } else {
172                // The boolean operator is equal to the previous one, so
173                // the counter is not incremented.
174                structural
175            }
176        } else {
177            // Save the first boolean operator in a sequence of
178            // logical operators and increment the counter.
179            self.boolean_op = Some(bool_id);
180            structural + 1
181        }
182    }
183}
184
185#[inline(always)]
186fn increment(stats: &mut Stats) {
187    stats.structural += stats.nesting + 1;
188}
189
190#[inline(always)]
191fn increment_by_one(stats: &mut Stats) {
192    stats.structural += 1;
193}
194
195fn get_nesting_from_map(
196    node: &Node,
197    nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
198) -> (usize, usize, usize) {
199    if let Some(parent) = node.object().parent() {
200        if let Some(n) = nesting_map.get(&parent.id()) {
201            *n
202        } else {
203            (0, 0, 0)
204        }
205    } else {
206        (0, 0, 0)
207    }
208}
209
210fn increment_function_depth<T: std::cmp::PartialEq + std::convert::From<u16>>(
211    depth: &mut usize,
212    node: &Node,
213    stop: T,
214) {
215    // Increase depth function nesting if needed
216    let mut child = node.object();
217    while let Some(parent) = child.parent() {
218        if stop == parent.kind_id().into() {
219            *depth += 1;
220            break;
221        }
222        child = parent;
223    }
224}
225
226#[inline(always)]
227fn increase_nesting(stats: &mut Stats, nesting: &mut usize, depth: usize, lambda: usize) {
228    stats.nesting = *nesting + depth + lambda;
229    increment(stats);
230    *nesting += 1;
231    stats.boolean_seq.reset();
232}
233
234impl Cognitive for PythonCode {
235    fn compute(
236        node: &Node,
237        stats: &mut Stats,
238        nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
239    ) {
240        use Python::*;
241
242        // Get nesting of the parent
243        let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
244
245        match node.object().kind_id().into() {
246            IfStatement | ForStatement | WhileStatement | ConditionalExpression => {
247                increase_nesting(stats, &mut nesting, depth, lambda);
248            }
249            ElifClause => {
250                // No nesting increment for them because their cost has already
251                // been paid by the if construct
252                increment_by_one(stats);
253                // Reset the boolean sequence
254                stats.boolean_seq.reset();
255            }
256            ElseClause | FinallyClause => {
257                // No nesting increment for them because their cost has already
258                // been paid by the if construct
259                increment_by_one(stats);
260            }
261            ExceptClause => {
262                nesting += 1;
263                increment(stats);
264            }
265            ExpressionList | ExpressionStatement | Tuple => {
266                stats.boolean_seq.reset();
267            }
268            NotOperator => {
269                stats.boolean_seq.not_operator(node.object().kind_id());
270            }
271            BooleanOperator => {
272                if count_specific_ancestors!(node, BooleanOperator, Lambda) == 0 {
273                    stats.structural += count_specific_ancestors!(
274                        node,
275                        Lambda,
276                        ExpressionList | IfStatement | ForStatement | WhileStatement
277                    );
278                }
279                compute_booleans::<language_python::Python>(node, stats, And, Or);
280            }
281            Lambda => {
282                // Increase lambda nesting
283                lambda += 1;
284            }
285            FunctionDefinition => {
286                // Increase depth function nesting if needed
287                increment_function_depth::<language_python::Python>(
288                    &mut depth,
289                    node,
290                    FunctionDefinition,
291                );
292            }
293            _ => {}
294        }
295        // Add node to nesting map
296        nesting_map.insert(node.object().id(), (nesting, depth, lambda));
297    }
298}
299
300impl Cognitive for RustCode {
301    fn compute(
302        node: &Node,
303        stats: &mut Stats,
304        nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
305    ) {
306        use Rust::*;
307        //TODO: Implement macros
308        let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
309
310        match node.object().kind_id().into() {
311            IfExpression => {
312                // Check if a node is not an else-if
313                if !Self::is_else_if(node) {
314                    increase_nesting(stats,&mut nesting, depth, lambda);
315                }
316            }
317            ForExpression | WhileExpression | MatchExpression => {
318                increase_nesting(stats,&mut nesting, depth, lambda);
319            }
320            Else /*else-if also */ => {
321                increment_by_one(stats);
322            }
323            BreakExpression | ContinueExpression => {
324                if let Some(label_child) = node.object().child(1) {
325                    if let LoopLabel = label_child.kind_id().into() {
326                        increment_by_one(stats);
327                    }
328                }
329            }
330            UnaryExpression => {
331                stats.boolean_seq.not_operator(node.object().kind_id());
332            }
333            BinaryExpression => {
334                compute_booleans::<language_rust::Rust>(node, stats, AMPAMP, PIPEPIPE);
335            }
336            FunctionItem  => {
337                nesting = 0;
338                // Increase depth function nesting if needed
339                increment_function_depth::<language_rust::Rust>(&mut depth, node, FunctionItem);
340            }
341            ClosureExpression => {
342                lambda += 1;
343            }
344            _ => {}
345        }
346        nesting_map.insert(node.object().id(), (nesting, depth, lambda));
347    }
348}
349
350impl Cognitive for CppCode {
351    fn compute(
352        node: &Node,
353        stats: &mut Stats,
354        nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
355    ) {
356        use Cpp::*;
357
358        //TODO: Implement macros
359        let (mut nesting, depth, mut lambda) = get_nesting_from_map(node, nesting_map);
360
361        match node.object().kind_id().into() {
362            IfStatement => {
363                if !Self::is_else_if(node) {
364                    increase_nesting(stats,&mut nesting, depth, lambda);
365                }
366            }
367            ForStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause => {
368                increase_nesting(stats,&mut nesting, depth, lambda);
369            }
370            GotoStatement | Else /* else-if also */ => {
371                increment_by_one(stats);
372            }
373            UnaryExpression2 => {
374                stats.boolean_seq.not_operator(node.object().kind_id());
375            }
376            BinaryExpression2 => {
377                compute_booleans::<language_cpp::Cpp>(node, stats, AMPAMP, PIPEPIPE);
378            }
379            LambdaExpression => {
380                lambda += 1;
381            }
382            _ => {}
383        }
384        nesting_map.insert(node.object().id(), (nesting, depth, lambda));
385    }
386}
387
388macro_rules! js_cognitive {
389    ($lang:ident) => {
390        fn compute(node: &Node, stats: &mut Stats, nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>) {
391            use $lang::*;
392            let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
393
394            match node.object().kind_id().into() {
395                IfStatement => {
396                    if !Self::is_else_if(&node) {
397                        increase_nesting(stats,&mut nesting, depth, lambda);
398                    }
399                }
400                ForStatement | ForInStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause | TernaryExpression => {
401                    increase_nesting(stats,&mut nesting, depth, lambda);
402                }
403                Else /* else-if also */ => {
404                    increment_by_one(stats);
405                }
406                ExpressionStatement => {
407                    // Reset the boolean sequence
408                    stats.boolean_seq.reset();
409                }
410                UnaryExpression => {
411                    stats.boolean_seq.not_operator(node.object().kind_id());
412                }
413                BinaryExpression => {
414                    compute_booleans::<$lang>(node, stats, AMPAMP, PIPEPIPE);
415                }
416                FunctionDeclaration => {
417                    // Reset lambda nesting at function for JS
418                    nesting = 0;
419                    lambda = 0;
420                    // Increase depth function nesting if needed
421                    increment_function_depth::<$lang>(&mut depth, node, FunctionDeclaration);
422                }
423                ArrowFunction => {
424                    lambda += 1;
425                }
426                _ => {}
427            }
428            nesting_map.insert(node.object().id(), (nesting, depth, lambda));
429        }
430    };
431}
432
433impl Cognitive for MozjsCode {
434    js_cognitive!(Mozjs);
435}
436
437impl Cognitive for JavascriptCode {
438    js_cognitive!(Javascript);
439}
440
441impl Cognitive for TypescriptCode {
442    js_cognitive!(Typescript);
443}
444
445impl Cognitive for TsxCode {
446    js_cognitive!(Tsx);
447}
448
449impl Cognitive for PreprocCode {}
450impl Cognitive for CcommentCode {}
451impl Cognitive for JavaCode {}
452
453#[cfg(test)]
454mod tests {
455    use std::path::PathBuf;
456
457    use super::*;
458
459    #[test]
460    fn python_no_cognitive() {
461        check_metrics!(
462            "a = 42",
463            "foo.py",
464            PythonParser,
465            cognitive,
466            [
467                (cognitive_sum, 0, usize),
468                (cognitive_min, 0, usize),
469                (cognitive_max, 0, usize)
470            ],
471            [(cognitive_average, f64::NAN)]
472        );
473    }
474
475    #[test]
476    fn rust_no_cognitive() {
477        check_metrics!(
478            "let a = 42;",
479            "foo.rs",
480            RustParser,
481            cognitive,
482            [
483                (cognitive_sum, 0, usize),
484                (cognitive_min, 0, usize),
485                (cognitive_max, 0, usize)
486            ],
487            [(cognitive_average, f64::NAN)]
488        );
489    }
490
491    #[test]
492    fn c_no_cognitive() {
493        check_metrics!(
494            "int a = 42;",
495            "foo.c",
496            CppParser,
497            cognitive,
498            [
499                (cognitive_sum, 0, usize),
500                (cognitive_min, 0, usize),
501                (cognitive_max, 0, usize)
502            ],
503            [(cognitive_average, f64::NAN)]
504        );
505    }
506
507    #[test]
508    fn mozjs_no_cognitive() {
509        check_metrics!(
510            "var a = 42;",
511            "foo.js",
512            MozjsParser,
513            cognitive,
514            [
515                (cognitive_sum, 0, usize),
516                (cognitive_min, 0, usize),
517                (cognitive_max, 0, usize)
518            ],
519            [(cognitive_average, f64::NAN)]
520        );
521    }
522
523    #[test]
524    fn python_simple_function() {
525        check_metrics!(
526            "def f(a, b):
527                if a and b:  # +2 (+1 and)
528                   return 1
529                if c and d: # +2 (+1 and)
530                   return 1",
531            "foo.py",
532            PythonParser,
533            cognitive,
534            [
535                (cognitive_sum, 4, usize),
536                (cognitive_min, 0, usize),
537                (cognitive_max, 4, usize)
538            ],
539            [(cognitive_average, 4.0)]
540        );
541    }
542
543    #[test]
544    fn python_expression_statement() {
545        // Boolean expressions containing `And` and `Or` operators were not
546        // considered in assignments
547        check_metrics!(
548            "def f(a, b):
549                c = True and True",
550            "foo.py",
551            PythonParser,
552            cognitive,
553            [
554                (cognitive_sum, 1, usize),
555                (cognitive_min, 0, usize),
556                (cognitive_max, 1, usize)
557            ],
558            [(cognitive_average, 1.0)]
559        );
560    }
561
562    #[test]
563    fn python_tuple() {
564        // Boolean expressions containing `And` and `Or` operators were not
565        // considered inside tuples
566        check_metrics!(
567            "def f(a, b):
568                return \"%s%s\" % (a and \"Get\" or \"Set\", b)",
569            "foo.py",
570            PythonParser,
571            cognitive,
572            [
573                (cognitive_sum, 2, usize),
574                (cognitive_min, 0, usize),
575                (cognitive_max, 2, usize)
576            ],
577            [(cognitive_average, 2.0)]
578        );
579    }
580
581    #[test]
582    fn python_elif_function() {
583        // Boolean expressions containing `And` and `Or` operators were not
584        // considered in `elif` statements
585        check_metrics!(
586            "def f(a, b):
587                if a and b:  # +2 (+1 and)
588                   return 1
589                elif c and d: # +2 (+1 and)
590                   return 1",
591            "foo.py",
592            PythonParser,
593            cognitive,
594            [
595                (cognitive_sum, 4, usize),
596                (cognitive_min, 0, usize),
597                (cognitive_max, 4, usize)
598            ],
599            [(cognitive_average, 4.0)]
600        );
601    }
602
603    #[test]
604    fn python_more_elifs_function() {
605        // Boolean expressions containing `And` and `Or` operators were not
606        // considered when there were more `elif` statements
607        check_metrics!(
608            "def f(a, b):
609                if a and b:  # +2 (+1 and)
610                   return 1
611                elif c and d: # +2 (+1 and)
612                   return 1
613                elif e and f: # +2 (+1 and)
614                   return 1",
615            "foo.py",
616            PythonParser,
617            cognitive,
618            [
619                (cognitive_sum, 6, usize),
620                (cognitive_min, 0, usize),
621                (cognitive_max, 6, usize)
622            ],
623            [(cognitive_average, 6.0)]
624        );
625    }
626
627    #[test]
628    fn rust_simple_function() {
629        check_metrics!(
630            "fn f() {
631                 if a && b { // +2 (+1 &&)
632                     println!(\"test\");
633                 }
634                 if c && d { // +2 (+1 &&)
635                     println!(\"test\");
636                 }
637             }",
638            "foo.rs",
639            RustParser,
640            cognitive,
641            [
642                (cognitive_sum, 4, usize),
643                (cognitive_min, 0, usize),
644                (cognitive_max, 4, usize)
645            ],
646            [(cognitive_average, 4.0)]
647        );
648    }
649
650    #[test]
651    fn c_simple_function() {
652        check_metrics!(
653            "void f() {
654                 if (a && b) { // +2 (+1 &&)
655                     printf(\"test\");
656                 }
657                 if (c && d) { // +2 (+1 &&)
658                     printf(\"test\");
659                 }
660             }",
661            "foo.c",
662            CppParser,
663            cognitive,
664            [
665                (cognitive_sum, 4, usize),
666                (cognitive_min, 0, usize),
667                (cognitive_max, 4, usize)
668            ],
669            [(cognitive_average, 4.0)]
670        );
671    }
672
673    #[test]
674    fn mozjs_simple_function() {
675        check_metrics!(
676            "function f() {
677                 if (a && b) { // +2 (+1 &&)
678                     window.print(\"test\");
679                 }
680                 if (c && d) { // +2 (+1 &&)
681                     window.print(\"test\");
682                 }
683             }",
684            "foo.js",
685            MozjsParser,
686            cognitive,
687            [
688                (cognitive_sum, 4, usize),
689                (cognitive_min, 0, usize),
690                (cognitive_max, 4, usize)
691            ],
692            [(cognitive_average, 4.0)]
693        );
694    }
695
696    #[test]
697    fn python_sequence_same_booleans() {
698        check_metrics!(
699            "def f(a, b):
700                if a and b and True:  # +2 (+1 sequence of and)
701                   return 1",
702            "foo.py",
703            PythonParser,
704            cognitive,
705            [
706                (cognitive_sum, 2, usize),
707                (cognitive_min, 0, usize),
708                (cognitive_max, 2, usize)
709            ],
710            [(cognitive_average, 2.0)]
711        );
712    }
713
714    #[test]
715    fn rust_sequence_same_booleans() {
716        check_metrics!(
717            "fn f() {
718                 if a && b && true { // +2 (+1 sequence of &&)
719                     println!(\"test\");
720                 }
721             }",
722            "foo.rs",
723            RustParser,
724            cognitive,
725            [
726                (cognitive_sum, 2, usize),
727                (cognitive_min, 0, usize),
728                (cognitive_max, 2, usize)
729            ],
730            [(cognitive_average, 2.0)]
731        );
732
733        check_metrics!(
734            "fn f() {
735                 if a || b || c || d { // +2 (+1 sequence of ||)
736                     println!(\"test\");
737                 }
738             }",
739            "foo.rs",
740            RustParser,
741            cognitive,
742            [
743                (cognitive_sum, 2, usize),
744                (cognitive_min, 0, usize),
745                (cognitive_max, 2, usize)
746            ],
747            [(cognitive_average, 2.0)]
748        );
749    }
750
751    #[test]
752    fn c_sequence_same_booleans() {
753        check_metrics!(
754            "void f() {
755                 if (a && b && 1 == 1) { // +2 (+1 sequence of &&)
756                     printf(\"test\");
757                 }
758             }",
759            "foo.c",
760            CppParser,
761            cognitive,
762            [
763                (cognitive_sum, 2, usize),
764                (cognitive_min, 0, usize),
765                (cognitive_max, 2, usize)
766            ],
767            [(cognitive_average, 2.0)]
768        );
769
770        check_metrics!(
771            "void f() {
772                 if (a || b || c || d) { // +2 (+1 sequence of ||)
773                     printf(\"test\");
774                 }
775             }",
776            "foo.c",
777            CppParser,
778            cognitive,
779            [
780                (cognitive_sum, 2, usize),
781                (cognitive_min, 0, usize),
782                (cognitive_max, 2, usize)
783            ],
784            [(cognitive_average, 2.0)]
785        );
786    }
787
788    #[test]
789    fn mozjs_sequence_same_booleans() {
790        check_metrics!(
791            "function f() {
792                 if (a && b && 1 == 1) { // +2 (+1 sequence of &&)
793                     window.print(\"test\");
794                 }
795             }",
796            "foo.js",
797            MozjsParser,
798            cognitive,
799            [
800                (cognitive_sum, 2, usize),
801                (cognitive_min, 0, usize),
802                (cognitive_max, 2, usize)
803            ],
804            [(cognitive_average, 2.0)]
805        );
806
807        check_metrics!(
808            "function f() {
809                 if (a || b || c || d) { // +2 (+1 sequence of ||)
810                     window.print(\"test\");
811                 }
812             }",
813            "foo.js",
814            MozjsParser,
815            cognitive,
816            [
817                (cognitive_sum, 2, usize),
818                (cognitive_min, 0, usize),
819                (cognitive_max, 2, usize)
820            ],
821            [(cognitive_average, 2.0)]
822        );
823    }
824
825    #[test]
826    fn rust_not_booleans() {
827        check_metrics!(
828            "fn f() {
829                 if !a && !b { // +2 (+1 &&)
830                     println!(\"test\");
831                 }
832             }",
833            "foo.rs",
834            RustParser,
835            cognitive,
836            [
837                (cognitive_sum, 2, usize),
838                (cognitive_min, 0, usize),
839                (cognitive_max, 2, usize)
840            ],
841            [(cognitive_average, 2.0)]
842        );
843
844        check_metrics!(
845            "fn f() {
846                 if a && !(b && c) { // +3 (+1 &&, +1 &&)
847                     println!(\"test\");
848                 }
849             }",
850            "foo.rs",
851            RustParser,
852            cognitive,
853            [
854                (cognitive_sum, 3, usize),
855                (cognitive_min, 0, usize),
856                (cognitive_max, 3, usize)
857            ],
858            [(cognitive_average, 3.0)]
859        );
860
861        check_metrics!(
862            "fn f() {
863                 if !(a || b) && !(c || d) { // +4 (+1 ||, +1 &&, +1 ||)
864                     println!(\"test\");
865                 }
866             }",
867            "foo.rs",
868            RustParser,
869            cognitive,
870            [
871                (cognitive_sum, 4, usize),
872                (cognitive_min, 0, usize),
873                (cognitive_max, 4, usize)
874            ],
875            [(cognitive_average, 4.0)]
876        );
877    }
878
879    #[test]
880    fn c_not_booleans() {
881        check_metrics!(
882            "void f() {
883                 if (a && !(b && c)) { // +3 (+1 &&, +1 &&)
884                     printf(\"test\");
885                 }
886             }",
887            "foo.c",
888            CppParser,
889            cognitive,
890            [
891                (cognitive_sum, 3, usize),
892                (cognitive_min, 0, usize),
893                (cognitive_max, 3, usize)
894            ],
895            [(cognitive_average, 3.0)]
896        );
897
898        check_metrics!(
899            "void f() {
900                 if (!(a || b) && !(c || d)) { // +4 (+1 ||, +1 &&, +1 ||)
901                     printf(\"test\");
902                 }
903             }",
904            "foo.c",
905            CppParser,
906            cognitive,
907            [
908                (cognitive_sum, 4, usize),
909                (cognitive_min, 0, usize),
910                (cognitive_max, 4, usize)
911            ],
912            [(cognitive_average, 4.0)]
913        );
914    }
915
916    #[test]
917    fn mozjs_not_booleans() {
918        check_metrics!(
919            "function f() {
920                 if (a && !(b && c)) { // +3 (+1 &&, +1 &&)
921                     window.print(\"test\");
922                 }
923             }",
924            "foo.js",
925            MozjsParser,
926            cognitive,
927            [
928                (cognitive_sum, 3, usize),
929                (cognitive_min, 0, usize),
930                (cognitive_max, 3, usize)
931            ],
932            [(cognitive_average, 3.0)]
933        );
934
935        check_metrics!(
936            "function f() {
937                 if (!(a || b) && !(c || d)) { // +4 (+1 ||, +1 &&, +1 ||)
938                     window.print(\"test\");
939                 }
940             }",
941            "foo.js",
942            MozjsParser,
943            cognitive,
944            [
945                (cognitive_sum, 4, usize),
946                (cognitive_min, 0, usize),
947                (cognitive_max, 4, usize)
948            ],
949            [(cognitive_average, 4.0)]
950        );
951    }
952
953    #[test]
954    fn python_sequence_different_booleans() {
955        check_metrics!(
956            "def f(a, b):
957                if a and b or True:  # +3 (+1 and, +1 or)
958                   return 1",
959            "foo.py",
960            PythonParser,
961            cognitive,
962            [
963                (cognitive_sum, 3, usize),
964                (cognitive_min, 0, usize),
965                (cognitive_max, 3, usize)
966            ],
967            [(cognitive_average, 3.0)]
968        );
969    }
970
971    #[test]
972    fn rust_sequence_different_booleans() {
973        check_metrics!(
974            "fn f() {
975                 if a && b || true { // +3 (+1 &&, +1 ||)
976                     println!(\"test\");
977                 }
978             }",
979            "foo.rs",
980            RustParser,
981            cognitive,
982            [
983                (cognitive_sum, 3, usize),
984                (cognitive_min, 0, usize),
985                (cognitive_max, 3, usize)
986            ],
987            [(cognitive_average, 3.0)]
988        );
989    }
990
991    #[test]
992    fn c_sequence_different_booleans() {
993        check_metrics!(
994            "void f() {
995                 if (a && b || 1 == 1) { // +3 (+1 &&, +1 ||)
996                     printf(\"test\");
997                 }
998             }",
999            "foo.c",
1000            CppParser,
1001            cognitive,
1002            [
1003                (cognitive_sum, 3, usize),
1004                (cognitive_min, 0, usize),
1005                (cognitive_max, 3, usize)
1006            ],
1007            [(cognitive_average, 3.0)]
1008        );
1009    }
1010
1011    #[test]
1012    fn mozjs_sequence_different_booleans() {
1013        check_metrics!(
1014            "function f() {
1015                 if (a && b || 1 == 1) { // +3 (+1 &&, +1 ||)
1016                     window.print(\"test\");
1017                 }
1018             }",
1019            "foo.js",
1020            MozjsParser,
1021            cognitive,
1022            [
1023                (cognitive_sum, 3, usize),
1024                (cognitive_min, 0, usize),
1025                (cognitive_max, 3, usize)
1026            ],
1027            [(cognitive_average, 3.0)]
1028        );
1029    }
1030
1031    #[test]
1032    fn python_formatted_sequence_different_booleans() {
1033        check_metrics!(
1034            "def f(a, b):
1035                if (  # +1
1036                    a and b and  # +1
1037                    (c or d)  # +1
1038                ):
1039                   return 1",
1040            "foo.py",
1041            PythonParser,
1042            cognitive,
1043            [
1044                (cognitive_sum, 3, usize),
1045                (cognitive_min, 0, usize),
1046                (cognitive_max, 3, usize)
1047            ],
1048            [(cognitive_average, 3.0)]
1049        );
1050    }
1051
1052    #[test]
1053    fn python_1_level_nesting() {
1054        check_metrics!(
1055            "def f(a, b):
1056                if a:  # +1
1057                    for i in range(b):  # +2
1058                        return 1",
1059            "foo.py",
1060            PythonParser,
1061            cognitive,
1062            [
1063                (cognitive_sum, 3, usize),
1064                (cognitive_min, 0, usize),
1065                (cognitive_max, 3, usize)
1066            ],
1067            [(cognitive_average, 3.0)]
1068        );
1069    }
1070
1071    #[test]
1072    fn rust_1_level_nesting() {
1073        check_metrics!(
1074            "fn f() {
1075                 if true { // +1
1076                     if true { // +2 (nesting = 1)
1077                         println!(\"test\");
1078                     } else if 1 == 1 { // +1
1079                         if true { // +3 (nesting = 2)
1080                             println!(\"test\");
1081                         }
1082                     } else { // +1
1083                         if true { // +3 (nesting = 2)
1084                             println!(\"test\");
1085                         }
1086                     }
1087                 }
1088             }",
1089            "foo.rs",
1090            RustParser,
1091            cognitive,
1092            [
1093                (cognitive_sum, 11, usize),
1094                (cognitive_min, 0, usize),
1095                (cognitive_max, 11, usize)
1096            ],
1097            [(cognitive_average, 11.0)]
1098        );
1099
1100        check_metrics!(
1101            "fn f() {
1102                 if true { // +1
1103                     match true { // +2 (nesting = 1)
1104                         true => println!(\"test\"),
1105                         false => println!(\"test\"),
1106                     }
1107                 }
1108             }",
1109            "foo.rs",
1110            RustParser,
1111            cognitive,
1112            [
1113                (cognitive_sum, 3, usize),
1114                (cognitive_min, 0, usize),
1115                (cognitive_max, 3, usize)
1116            ],
1117            [(cognitive_average, 3.0)]
1118        );
1119    }
1120
1121    #[test]
1122    fn c_1_level_nesting() {
1123        check_metrics!(
1124            "void f() {
1125                 if (1 == 1) { // +1
1126                     if (1 == 1) { // +2 (nesting = 1)
1127                         printf(\"test\");
1128                     } else if (1 == 1) { // +1
1129                         if (1 == 1) { // +3 (nesting = 2)
1130                             printf(\"test\");
1131                         }
1132                     } else { // +1
1133                         if (1 == 1) { // +3 (nesting = 2)
1134                             printf(\"test\");
1135                         }
1136                     }
1137                 }
1138             }",
1139            "foo.c",
1140            CppParser,
1141            cognitive,
1142            [
1143                (cognitive_sum, 11, usize),
1144                (cognitive_min, 0, usize),
1145                (cognitive_max, 11, usize)
1146            ],
1147            [(cognitive_average, 11.0)]
1148        );
1149    }
1150
1151    #[test]
1152    fn mozjs_1_level_nesting() {
1153        check_metrics!(
1154            "function f() {
1155                 if (1 == 1) { // +1
1156                     if (1 == 1) { // +2 (nesting = 1)
1157                         window.print(\"test\");
1158                     } else if (1 == 1) { // +1
1159                         if (1 == 1) { // +3 (nesting = 2)
1160                             window.print(\"test\");
1161                         }
1162                     } else { // +1
1163                         if (1 == 1) { // +3 (nesting = 2)
1164                             window.print(\"test\");
1165                         }
1166                     }
1167                 }
1168             }",
1169            "foo.js",
1170            MozjsParser,
1171            cognitive,
1172            [
1173                (cognitive_sum, 11, usize),
1174                (cognitive_min, 0, usize),
1175                (cognitive_max, 11, usize)
1176            ],
1177            [(cognitive_average, 11.0)]
1178        );
1179    }
1180
1181    #[test]
1182    fn python_2_level_nesting() {
1183        check_metrics!(
1184            "def f(a, b):
1185                if a:  # +1
1186                    for i in range(b):  # +2
1187                        if b:  # +3
1188                            return 1",
1189            "foo.py",
1190            PythonParser,
1191            cognitive,
1192            [
1193                (cognitive_sum, 6, usize),
1194                (cognitive_min, 0, usize),
1195                (cognitive_max, 6, usize)
1196            ],
1197            [(cognitive_average, 6.0)]
1198        );
1199    }
1200
1201    #[test]
1202    fn rust_2_level_nesting() {
1203        check_metrics!(
1204            "fn f() {
1205                 if true { // +1
1206                     for i in 0..4 { // +2 (nesting = 1)
1207                         match true { // +3 (nesting = 2)
1208                             true => println!(\"test\"),
1209                             false => println!(\"test\"),
1210                         }
1211                     }
1212                 }
1213             }",
1214            "foo.rs",
1215            RustParser,
1216            cognitive,
1217            [
1218                (cognitive_sum, 6, usize),
1219                (cognitive_min, 0, usize),
1220                (cognitive_max, 6, usize)
1221            ],
1222            [(cognitive_average, 6.0)]
1223        );
1224    }
1225
1226    #[test]
1227    fn python_try_construct() {
1228        check_metrics!(
1229            "def f(a, b):
1230                try:
1231                    for foo in bar:  # +1
1232                        return a
1233                except Exception:  # +1
1234                    if a < 0:  # +2
1235                        return a",
1236            "foo.py",
1237            PythonParser,
1238            cognitive,
1239            [
1240                (cognitive_sum, 4, usize),
1241                (cognitive_min, 0, usize),
1242                (cognitive_max, 4, usize)
1243            ],
1244            [(cognitive_average, 4.0)]
1245        );
1246    }
1247
1248    #[test]
1249    fn mozjs_try_construct() {
1250        check_metrics!(
1251            "function asyncOnChannelRedirect(oldChannel, newChannel, flags, callback) {
1252                 for (const collector of this.collectors) {
1253                     try {
1254                         collector._onChannelRedirect(oldChannel, newChannel, flags);
1255                     } catch (ex) {
1256                         console.error(
1257                             \"StackTraceCollector.onChannelRedirect threw an exception\",
1258                              ex
1259                         );
1260                     }
1261                 }
1262                 callback.onRedirectVerifyCallback(Cr.NS_OK);
1263             }",
1264            "foo.js",
1265            MozjsParser,
1266            cognitive,
1267            [
1268                (cognitive_sum, 3, usize),
1269                (cognitive_min, 0, usize),
1270                (cognitive_max, 3, usize)
1271            ],
1272            [(cognitive_average, 3.0)]
1273        );
1274    }
1275
1276    #[test]
1277    fn rust_break_continue() {
1278        // Only labeled break and continue statements are considered
1279        check_metrics!(
1280            "fn f() {
1281                 'tens: for ten in 0..3 { // +1
1282                     '_units: for unit in 0..=9 { // +2 (nesting = 1)
1283                         if unit % 2 == 0 { // +3 (nesting = 2)
1284                             continue;
1285                         } else if unit == 5 { // +1
1286                             continue 'tens; // +1
1287                         } else if unit == 6 { // +1
1288                             break;
1289                         } else { // +1
1290                             break 'tens; // +1
1291                         }
1292                     }
1293                 }
1294             }",
1295            "foo.rs",
1296            RustParser,
1297            cognitive,
1298            [
1299                (cognitive_sum, 11, usize),
1300                (cognitive_min, 0, usize),
1301                (cognitive_max, 11, usize)
1302            ],
1303            [(cognitive_average, 11.0)]
1304        );
1305    }
1306
1307    #[test]
1308    fn c_goto() {
1309        check_metrics!(
1310            "void f() {
1311             OUT: for (int i = 1; i <= max; ++i) { // +1
1312                      for (int j = 2; j < i; ++j) { // +2 (nesting = 1)
1313                          if (i % j == 0) { // +3 (nesting = 2)
1314                              goto OUT; // +1
1315                          }
1316                      }
1317                  }
1318             }",
1319            "foo.c",
1320            CppParser,
1321            cognitive,
1322            [
1323                (cognitive_sum, 7, usize),
1324                (cognitive_min, 0, usize),
1325                (cognitive_max, 7, usize)
1326            ],
1327            [(cognitive_average, 7.0)]
1328        );
1329    }
1330
1331    #[test]
1332    fn c_switch() {
1333        check_metrics!(
1334            "void f() {
1335                 switch (1) { // +1
1336                     case 1:
1337                         printf(\"one\");
1338                         break;
1339                     case 2:
1340                         printf(\"two\");
1341                         break;
1342                     case 3:
1343                         printf(\"three\");
1344                         break;
1345                     default:
1346                         printf(\"all\");
1347                         break;
1348                 }
1349             }",
1350            "foo.c",
1351            CppParser,
1352            cognitive,
1353            [
1354                (cognitive_sum, 1, usize),
1355                (cognitive_min, 0, usize),
1356                (cognitive_max, 1, usize)
1357            ],
1358            [(cognitive_average, 1.0)]
1359        );
1360    }
1361
1362    #[test]
1363    fn mozjs_switch() {
1364        check_metrics!(
1365            "function f() {
1366                 switch (1) { // +1
1367                     case 1:
1368                         window.print(\"one\");
1369                         break;
1370                     case 2:
1371                         window.print(\"two\");
1372                         break;
1373                     case 3:
1374                         window.print(\"three\");
1375                         break;
1376                     default:
1377                         window.print(\"all\");
1378                         break;
1379                 }
1380             }",
1381            "foo.js",
1382            MozjsParser,
1383            cognitive,
1384            [
1385                (cognitive_sum, 1, usize),
1386                (cognitive_min, 0, usize),
1387                (cognitive_max, 1, usize)
1388            ],
1389            [(cognitive_average, 1.0)]
1390        );
1391    }
1392
1393    #[test]
1394    fn python_ternary_operator() {
1395        check_metrics!(
1396            "def f(a, b):
1397                 if a % 2:  # +1
1398                     return 'c' if a else 'd'  # +2
1399                 return 'a' if a else 'b'  # +1",
1400            "foo.py",
1401            PythonParser,
1402            cognitive,
1403            [
1404                (cognitive_sum, 4, usize),
1405                (cognitive_min, 0, usize),
1406                (cognitive_max, 4, usize)
1407            ],
1408            [(cognitive_average, 4.0)]
1409        );
1410    }
1411
1412    #[test]
1413    fn python_nested_functions_lambdas() {
1414        check_metrics!(
1415            "def f(a, b):
1416                 def foo(a):
1417                     if a:  # +2 (+1 nesting)
1418                         return 1
1419                 # +3 (+1 for boolean sequence +2 for lambda nesting)
1420                 bar = lambda a: lambda b: b or True or True
1421                 return bar(foo(a))(a)",
1422            "foo.py",
1423            PythonParser,
1424            cognitive,
1425            [
1426                (cognitive_sum, 5, usize),
1427                (cognitive_min, 0, usize),
1428                (cognitive_max, 3, usize)
1429            ],
1430            [(cognitive_average, 1.25)] // 2 functions + 2 lamdas = 4
1431        );
1432    }
1433
1434    #[test]
1435    fn python_real_function() {
1436        check_metrics!(
1437            "def process_raw_constant(constant, min_word_length):
1438                 processed_words = []
1439                 raw_camelcase_words = []
1440                 for raw_word in re.findall(r'[a-z]+', constant):  # +1
1441                     word = raw_word.strip()
1442                         if (  # +2 (+1 if and +1 nesting)
1443                             len(word) >= min_word_length
1444                             and not (word.startswith('-') or word.endswith('-')) # +2 operators
1445                         ):
1446                             if is_camel_case_word(word):  # +3 (+1 if and +2 nesting)
1447                                 raw_camelcase_words.append(word)
1448                             else: # +1 else
1449                                 processed_words.append(word.lower())
1450                 return processed_words, raw_camelcase_words",
1451            "foo.py",
1452            PythonParser,
1453            cognitive,
1454            [
1455                (cognitive_sum, 9, usize),
1456                (cognitive_min, 0, usize),
1457                (cognitive_max, 9, usize)
1458            ],
1459            [(cognitive_average, 9.0)]
1460        );
1461    }
1462
1463    #[test]
1464    fn rust_if_let_else_if_else() {
1465        check_metrics!(
1466            "pub fn create_usage_no_title(p: &Parser, used: &[&str]) -> String {
1467                 debugln!(\"usage::create_usage_no_title;\");
1468                 if let Some(u) = p.meta.usage_str { // +1
1469                     String::from(&*u)
1470                 } else if used.is_empty() { // +1
1471                     create_help_usage(p, true)
1472                 } else { // +1
1473                     create_smart_usage(p, used)
1474                }
1475            }",
1476            "foo.rs",
1477            RustParser,
1478            cognitive,
1479            [
1480                (cognitive_sum, 3, usize),
1481                (cognitive_min, 0, usize),
1482                (cognitive_max, 3, usize)
1483            ],
1484            [(cognitive_average, 3.0)]
1485        );
1486    }
1487
1488    #[test]
1489    fn typescript_if_else_if_else() {
1490        check_metrics!(
1491            "function foo() {
1492                 if (this._closed) return Promise.resolve(); // +1
1493                 if (this._tempDirectory) { // +1
1494                     this.kill();
1495                 } else if (this.connection) { // +1
1496                     this.kill();
1497                 } else { // +1
1498                     throw new Error(`Error`);
1499                }
1500                helper.removeEventListeners(this._listeners);
1501                return this._processClosing;
1502            }",
1503            "foo.ts",
1504            TypescriptParser,
1505            cognitive,
1506            [
1507                (cognitive_sum, 4, usize),
1508                (cognitive_min, 0, usize),
1509                (cognitive_max, 4, usize)
1510            ],
1511            [(cognitive_average, 4.0)]
1512        );
1513    }
1514}