complexity/
lib.rs

1//! Calculate cognitive complexity of Rust code.
2//!
3//! Based on [Cognitive Complexity][pdf] by G. Ann Campbell.
4//!
5//! ## Getting started
6//!
7//! You'll need to bring the [`Complexity`] trait into scope, and probably some
8//! things from [`syn`].
9//!
10//! ```rust
11//! use complexity::Complexity;
12//! use syn::{Expr, parse_quote};
13//! ```
14//!
15//! Complexity of expressions and other [`syn`] types is as simple as calling
16//! [`.complexity()`] on an instance of that type.
17//!
18//! ```rust
19//! # use complexity::Complexity;
20//! # use syn::{Expr, parse_quote};
21//! let expr: Expr = parse_quote! {
22//!     for element in iterable { // +1
23//!         if something {        // +2 (nesting = 1)
24//!             do_something();
25//!         }
26//!     }
27//! };
28//! assert_eq!(expr.complexity(), 3);
29//! ```
30//!
31//! ## Examples
32//!
33//! The implementation of cognitive complexity in this crate is heavily based on
34//! [Cognitive Complexity][pdf] by G. Ann Campbell. And reading it would be
35//! beneficial to understanding how the complexity index is calculated.
36//!
37//! Loops and structures that introduce branching increment the complexity by
38//! one each. Some syntax structures introduce a "nesting" level which increases
39//! some expressions complexity by that nesting level in addition to their
40//! regular increment. In the example below we see how two nested loops and an
41//! if statement can produce quite a high complexity of **7**.
42//!
43//! ```rust
44//! use complexity::Complexity;
45//! use syn::{ItemFn, parse_quote};
46//!
47//! let func: ItemFn = parse_quote! {
48//!     fn sum_of_primes(max: u64) -> u64 {
49//!         let mut total = 0;
50//!         'outer: for i in 1..=max {   // +1
51//!             for j in 2..i {          // +2 (nesting = 1)
52//!                 if i % j == 0 {      // +3 (nesting = 2)
53//!                     continue 'outer; // +1
54//!                 }
55//!             }
56//!             total += i;
57//!         }
58//!     }
59//! };
60//! assert_eq!(func.complexity(), 7);
61//! ```
62//!
63//! But some structures are rewarded. Particularly a `match` statement, which
64//! only increases the complexity by one no matter how many branches there are.
65//! (It does increase the nesting level though.) In the example below we see how
66//! even though there are a lot of branches in the code (which would contribute
67//! a lot to a more traditional *cyclomatic complexity* measurement), the
68//! complexity is quite low at **1**.
69//!
70//! ```rust
71//! use complexity::Complexity;
72//! use syn::{ItemFn, parse_quote};
73//!
74//! let func: ItemFn = parse_quote! {
75//!     fn get_words(number: u64) -> &str {
76//!         match number {       // +1
77//!             1 => "one",
78//!             2 => "a couple",
79//!             3 => "a few",
80//!             _ => "lots",
81//!         }
82//!     }
83//! };
84//! assert_eq!(func.complexity(), 1);
85//! ```
86//!
87//! An example is provided to calculate and nicely print out the cognitive
88//! complexity of each function and method in an entire Rust file. See
89//! [examples/lint-files.rs](examples/lint-files.rs). You can run it on Rust
90//! files like this:
91//!
92//! ```sh
93//! cargo run --example lint-files -- src/
94//! ```
95//!
96//! [pdf]: https://www.sonarsource.com/docs/CognitiveComplexity.pdf
97//! [`Complexity`]: trait.Complexity.html
98//! [`.complexity()`]: trait.Complexity.html#tymethod.complexity
99//! [`syn`]: https://docs.rs/syn/1
100
101use std::{iter, ops};
102
103use syn::*;
104
105/////////////////////////////////////////////////////////////////////////
106// Complexity trait
107/////////////////////////////////////////////////////////////////////////
108
109mod private {
110    pub trait Sealed {}
111    impl Sealed for super::Expr {}
112    impl Sealed for super::ItemFn {}
113    impl Sealed for super::ImplItemMethod {}
114}
115
116/// A trait for calculating the cognitive complexity of a Rust type.
117///
118/// This is a *sealed* trait so only this crate can implement it.
119pub trait Complexity: private::Sealed {
120    /// Returns the cognitive complexity index for the implementor.
121    fn complexity(&self) -> u32;
122}
123
124impl Complexity for Expr {
125    fn complexity(&self) -> u32 {
126        eval_expr(self, State::default()).0
127    }
128}
129
130impl Complexity for ItemFn {
131    fn complexity(&self) -> u32 {
132        eval_block(&self.block, State::default()).0
133    }
134}
135
136impl Complexity for ImplItemMethod {
137    fn complexity(&self) -> u32 {
138        eval_block(&self.block, State::default()).0
139    }
140}
141
142/////////////////////////////////////////////////////////////////////////
143// Index type
144/////////////////////////////////////////////////////////////////////////
145
146/// Represents a complexity index.
147#[derive(Debug, Clone, Copy)]
148struct Index(u32);
149
150impl Index {
151    /// Construct a new zero `Index` that does not contribute to complexity.
152    fn zero() -> Self {
153        Self(0)
154    }
155
156    /// Construct a new `Index` that adds one to the complexity.
157    fn one() -> Self {
158        Self(1)
159    }
160
161    /// Construct a new `Index` that adds one to the complexity and one for each
162    /// level of nesting.
163    fn with_context(state: State) -> Self {
164        Self(state.nesting + 1)
165    }
166}
167
168impl ops::Add for Index {
169    type Output = Self;
170
171    /// Add one `Index` to another.
172    ///
173    /// This simply is the addition of both complexities.
174    fn add(self, other: Self) -> Self {
175        Self(self.0 + other.0)
176    }
177}
178
179impl iter::Sum<Index> for Index {
180    /// Sum an iterable of `Index` by simply accumulating all the complexities
181    /// into one.
182    fn sum<I>(iter: I) -> Self
183    where
184        I: Iterator<Item = Self>,
185    {
186        iter.fold(Self::zero(), ops::Add::add)
187    }
188}
189
190/////////////////////////////////////////////////////////////////////////
191// Logical boolean operator type
192/////////////////////////////////////////////////////////////////////////
193
194/// Represents a logical boolean operator.
195#[derive(Debug, Clone, Copy, PartialEq)]
196enum LogBoolOp {
197    /// The `!` operator (logical not).
198    Not,
199    /// The `&&` operator (logical and).
200    And,
201    /// The `||` operator (logical or).
202    Or,
203}
204
205impl LogBoolOp {
206    /// Create a new `LogBoolOp` from a `syn::UnOp`.
207    fn from_un_op(un_op: UnOp) -> Option<Self> {
208        match un_op {
209            UnOp::Not(_) => Some(Self::Not),
210            _ => None,
211        }
212    }
213
214    /// Create a new `LogBoolOp` from a `syn::BinOp`.
215    fn from_bin_op(bin_op: &BinOp) -> Option<Self> {
216        match bin_op {
217            BinOp::And(_) => Some(Self::And),
218            BinOp::Or(_) => Some(Self::Or),
219            _ => None,
220        }
221    }
222
223    /// Compares this `LogBoolOp` with the previous one and returns a complexity
224    /// index.
225    fn eval_based_on_prev(self, prev: Option<Self>) -> Index {
226        match (prev, self) {
227            (Some(prev), current) => {
228                if prev == current {
229                    Index::zero()
230                } else {
231                    Index::one()
232                }
233            }
234            (None, _) => Index::one(),
235        }
236    }
237}
238
239/////////////////////////////////////////////////////////////////////////
240// State type
241/////////////////////////////////////////////////////////////////////////
242
243/// Represents the current state during parsing. We use this type to track the
244/// nesting level and the previous logical boolean operator.
245#[derive(Debug, Default, Clone, Copy)]
246struct State {
247    /// The nesting level.
248    nesting: u32,
249    /// The previous logical boolean operator.
250    log_bool_op: Option<LogBoolOp>,
251}
252
253impl State {
254    /// Create a new `State` with an extra level of nesting.
255    fn increase_nesting(self) -> Self {
256        Self {
257            nesting: self.nesting + 1,
258            log_bool_op: self.log_bool_op,
259        }
260    }
261}
262
263/////////////////////////////////////////////////////////////////////////
264// Evaluation functions
265/////////////////////////////////////////////////////////////////////////
266
267/// Returns the complexity of a `syn::Block`.
268fn eval_block(block: &Block, state: State) -> Index {
269    block
270        .stmts
271        .iter()
272        .map(|e| eval_stmt(e, state))
273        .sum::<Index>()
274}
275
276/// Returns the complexity of a `syn::Stmt`.
277fn eval_stmt(stmt: &Stmt, state: State) -> Index {
278    match stmt {
279        Stmt::Local(Local {
280            init: Some((_, expr)),
281            ..
282        }) => eval_expr(expr, state),
283        Stmt::Local(Local { init: None, .. }) => Index::zero(),
284        Stmt::Item(item) => eval_item(item, state),
285        Stmt::Expr(expr) | Stmt::Semi(expr, _) => eval_expr(expr, state),
286    }
287}
288
289/// Returns the complexity of a `syn::Item`.
290fn eval_item(item: &Item, state: State) -> Index {
291    match item {
292        Item::Const(ItemConst { expr, .. }) => eval_expr(expr, state),
293        Item::Static(ItemStatic { expr, .. }) => eval_expr(expr, state),
294        _ => Index::zero(),
295    }
296}
297
298/// Returns the complexity of a `syn::ExprUnary`.
299///
300/// This function also updates the previous logical boolean operator if it is
301/// `!`.
302fn eval_expr_unary(expr_unary: &ExprUnary, mut state: State) -> Index {
303    let ExprUnary { op, expr, .. } = expr_unary;
304    if let Some(current) = LogBoolOp::from_un_op(*op) {
305        state.log_bool_op = Some(current);
306    }
307    eval_expr(expr, state)
308}
309
310/// Returns the complexity of a `syn::ExprBinary`.
311///
312/// This function handles logical boolean operators `&&` and `||` by doing the
313/// following:
314/// - If the operator is the different then add one to the complexity.
315/// - Update the previous logical boolean operator.
316fn eval_expr_binary(expr_binary: &ExprBinary, mut state: State) -> Index {
317    let ExprBinary {
318        left, op, right, ..
319    } = expr_binary;
320    let index = match LogBoolOp::from_bin_op(op) {
321        Some(current) => {
322            let index = current.eval_based_on_prev(state.log_bool_op);
323            state.log_bool_op = Some(current);
324            index
325        }
326        None => Index::zero(),
327    };
328    index + eval_expr(left, state) + eval_expr(right, state)
329}
330
331/// Returns the complexity of a `syn::ExprRange`.
332fn eval_expr_range(expr_range: &ExprRange, state: State) -> Index {
333    let ExprRange { from, to, .. } = expr_range;
334    from.as_ref()
335        .map(|e| eval_expr(e, state))
336        .unwrap_or_else(Index::zero)
337        + to.as_ref()
338            .map(|e| eval_expr(e, state))
339            .unwrap_or_else(Index::zero)
340}
341
342/// Returns the complexity of a `syn::ExprIf`.
343fn eval_expr_if(expr_if: &ExprIf, state: State) -> Index {
344    let ExprIf {
345        cond,
346        then_branch,
347        else_branch,
348        ..
349    } = expr_if;
350    Index::with_context(state)
351        + eval_expr(cond, state)
352        + eval_block(then_branch, state.increase_nesting())
353        + else_branch
354            .as_ref()
355            .map(|(_, expr)| Index::one() + eval_expr(&expr, state.increase_nesting()))
356            .unwrap_or_else(Index::zero)
357}
358
359/// Returns the complexity of a `syn::ExprMatch`.
360fn eval_expr_match(expr_match: &ExprMatch, state: State) -> Index {
361    let ExprMatch { expr, arms, .. } = expr_match;
362    Index::with_context(state)
363        + eval_expr(expr, state)
364        + arms
365            .iter()
366            .map(|arm| {
367                arm.guard
368                    .as_ref()
369                    .map(|(_, expr)| eval_expr(&expr, state))
370                    .unwrap_or_else(Index::zero)
371                    + eval_expr(&arm.body, state.increase_nesting())
372            })
373            .sum::<Index>()
374}
375
376/// Returns the complexity of a `syn::ExprForLoop`.
377fn eval_expr_for_loop(expr_for_loop: &ExprForLoop, state: State) -> Index {
378    let ExprForLoop { expr, body, .. } = expr_for_loop;
379    Index::with_context(state) + eval_expr(expr, state) + eval_block(body, state.increase_nesting())
380}
381
382/// Returns the complexity of a `syn::ExprWhile`.
383fn eval_expr_while(expr_while: &ExprWhile, state: State) -> Index {
384    let ExprWhile { cond, body, .. } = expr_while;
385    Index::with_context(state) + eval_expr(cond, state) + eval_block(body, state.increase_nesting())
386}
387
388/// Returns the complexity of a `syn::ExprStruct`.
389fn eval_expr_struct(expr_struct: &ExprStruct, state: State) -> Index {
390    let ExprStruct { fields, rest, .. } = expr_struct;
391    fields
392        .iter()
393        .map(|v| eval_expr(&v.expr, state))
394        .sum::<Index>()
395        + rest
396            .as_ref()
397            .map(|e| eval_expr(e, state))
398            .unwrap_or_else(Index::zero)
399}
400
401/// Returns the complexity of a `syn::ExprCall`.
402fn eval_expr_call(expr_call: &ExprCall, state: State) -> Index {
403    let ExprCall { func, args, .. } = expr_call;
404    eval_expr(func, state) + args.iter().map(|a| eval_expr(a, state)).sum::<Index>()
405}
406
407/// Returns the complexity of a `syn::ExprMethodCall`.
408fn eval_expr_method_call(expr_method_call: &ExprMethodCall, state: State) -> Index {
409    let ExprMethodCall { receiver, args, .. } = expr_method_call;
410    eval_expr(receiver, state) + args.iter().map(|a| eval_expr(a, state)).sum::<Index>()
411}
412
413/// Returns the complexity of a `syn::Expr`.
414///
415/// This function contains most of the logic for calculating cognitive
416/// complexity. Expressions that create nesting increase the complexity and
417/// expressions that increase the branching increasing the complexity.
418fn eval_expr(expr: &Expr, state: State) -> Index {
419    match expr {
420        // Expressions that map to multiple expressions.
421        // --------------------------------------------
422        Expr::Array(ExprArray { elems, .. }) | Expr::Tuple(ExprTuple { elems, .. }) => {
423            elems.iter().map(|e| eval_expr(e, state)).sum()
424        }
425
426        // Unary and binary operators.
427        // ---------------------------
428        // These are handled specially because of logical boolean operator complexity.
429        Expr::Unary(expr_unary) => eval_expr_unary(expr_unary, state),
430        Expr::Binary(expr_binary) => eval_expr_binary(expr_binary, state),
431
432        // Expressions that have a left and right part.
433        // --------------------------------------------
434        Expr::Assign(ExprAssign { left, right, .. })
435        | Expr::AssignOp(ExprAssignOp { left, right, .. })
436        | Expr::Index(ExprIndex {
437            expr: left,
438            index: right,
439            ..
440        })
441        | Expr::Repeat(ExprRepeat {
442            expr: left,
443            len: right,
444            ..
445        }) => eval_expr(left, state) + eval_expr(right, state),
446
447        Expr::Range(expr_range) => eval_expr_range(expr_range, state),
448
449        // Expressions that create a nested block like `async { .. }`.
450        // -----------------------------------------------------------
451        Expr::Async(ExprAsync { block, .. })
452        | Expr::Block(ExprBlock { block, .. })
453        | Expr::Loop(ExprLoop { body: block, .. })
454        | Expr::TryBlock(ExprTryBlock { block, .. })
455        | Expr::Unsafe(ExprUnsafe { block, .. }) => eval_block(block, state.increase_nesting()),
456        Expr::ForLoop(expr_for_loop) => eval_expr_for_loop(expr_for_loop, state),
457        Expr::While(expr_while) => eval_expr_while(expr_while, state),
458
459        // Expressions that do not not nest any further, and do not contribute to complexity.
460        // ----------------------------------------------------------------------------------
461        Expr::Lit(_) | Expr::Path(_) => Index::zero(),
462
463        // Expressions that wrap a single expression.
464        // ------------------------------------------
465        Expr::Await(ExprAwait { base: expr, .. })
466        | Expr::Box(ExprBox { expr, .. })
467        | Expr::Break(ExprBreak {
468            expr: Some(expr), ..
469        })
470        | Expr::Cast(ExprCast { expr, .. })
471        | Expr::Closure(ExprClosure { body: expr, .. })
472        | Expr::Field(ExprField { base: expr, .. })
473        | Expr::Group(ExprGroup { expr, .. })
474        | Expr::Let(ExprLet { expr, .. })
475        | Expr::Paren(ExprParen { expr, .. })
476        | Expr::Reference(ExprReference { expr, .. })
477        | Expr::Return(ExprReturn {
478            expr: Some(expr), ..
479        })
480        | Expr::Try(ExprTry { expr, .. })
481        | Expr::Type(ExprType { expr, .. })
482        | Expr::Yield(ExprYield {
483            expr: Some(expr), ..
484        }) => eval_expr(expr, state),
485
486        // Expressions that introduce branching.
487        // -------------------------------------
488        Expr::If(expr_if) => eval_expr_if(expr_if, state),
489        Expr::Match(expr_match) => eval_expr_match(expr_match, state),
490        Expr::Continue(_) | Expr::Break(_) => Index::one(),
491
492        // Expressions that call functions / construct types.
493        // --------------------------------------------------
494        Expr::Struct(expr_struct) => eval_expr_struct(expr_struct, state),
495        Expr::Call(expr_call) => eval_expr_call(expr_call, state),
496        Expr::MethodCall(expr_method_call) => eval_expr_method_call(expr_method_call, state),
497        // FIXME: should we attempt to parse macro the tokens into something that we can calculate
498        // the complexity for?
499        Expr::Macro(_) => Index::zero(),
500
501        // `Expr` is non-exhaustive, so this has to be here. But we should have handled everything.
502        _ => Index::zero(),
503    }
504}
505
506/////////////////////////////////////////////////////////////////////////
507// Unit tests
508/////////////////////////////////////////////////////////////////////////
509
510#[cfg(test)]
511mod tests {
512    use super::*;
513
514    #[test]
515    fn if_statement() {
516        let expr: Expr = parse_quote! {
517            if true {             // +1
518                println!("test");
519            }
520        };
521        assert_eq!(expr.complexity(), 1);
522    }
523
524    #[test]
525    fn if_statement_nesting_increment() {
526        let expr: Expr = parse_quote! {
527            if true {                 // +1
528                if true {             // +2 (nesting = 1)
529                    println!("test");
530                }
531            }
532        };
533        assert_eq!(expr.complexity(), 3);
534    }
535
536    #[test]
537    fn if_else_statement_no_nesting_increment() {
538        let expr: Expr = parse_quote! {
539            if true {                 // +1
540                if true {             // +2 (nesting = 1)
541                    println!("test");
542                } else {              // +1
543                    println!("test");
544                }
545            }
546        };
547        assert_eq!(expr.complexity(), 4);
548    }
549
550    #[test]
551    fn for_loop() {
552        let expr: Expr = parse_quote! {
553            for element in iterable { // +1
554                if true {             // +2 (nesting = 1)
555                    println!("test");
556                }
557            }
558        };
559        assert_eq!(expr.complexity(), 3);
560    }
561
562    #[test]
563    fn for_loop_nesting_increment() {
564        let expr: Expr = parse_quote! {
565            if true {                     // +1
566                for element in iterable { // +2 (nesting = 1)
567                    println!("test");
568                }
569            }
570        };
571        assert_eq!(expr.complexity(), 3);
572    }
573
574    #[test]
575    fn while_loop() {
576        let expr: Expr = parse_quote! {
577            while true {              // +1
578                if true {             // +2 (nesting = 1)
579                    println!("test");
580                }
581            }
582        };
583        assert_eq!(expr.complexity(), 3);
584    }
585
586    #[test]
587    fn while_loop_nesting_increment() {
588        let expr: Expr = parse_quote! {
589            if true {                 // +1
590                while true {          // +2 (nesting = 1)
591                    println!("test");
592                }
593            }
594        };
595        assert_eq!(expr.complexity(), 3);
596    }
597
598    #[test]
599    fn match_statement_nesting_increment() {
600        let expr: Expr = parse_quote! {
601            if true {                          // +1
602                match true {                   // +2 (nesting = 1)
603                    true => println!("test"),
604                    false => println!("test"),
605                }
606            }
607        };
608        assert_eq!(expr.complexity(), 3);
609    }
610
611    #[test]
612    fn logical_boolean_operators_same() {
613        let expr: Expr = parse_quote! { x && y };
614        assert_eq!(expr.complexity(), 1);
615        let expr: Expr = parse_quote! { x && y && z };
616        assert_eq!(expr.complexity(), 1);
617        let expr: Expr = parse_quote! { w && x && y && z };
618        assert_eq!(expr.complexity(), 1);
619        let expr: Expr = parse_quote! { x || y };
620        assert_eq!(expr.complexity(), 1);
621        let expr: Expr = parse_quote! { x || y || z };
622        assert_eq!(expr.complexity(), 1);
623        let expr: Expr = parse_quote! { w || x || y || z };
624        assert_eq!(expr.complexity(), 1);
625    }
626
627    #[test]
628    fn logical_boolean_operators_changing() {
629        let expr: Expr = parse_quote! { w && x || y || z };
630        assert_eq!(expr.complexity(), 2);
631        let expr: Expr = parse_quote! { w && x && y || z };
632        assert_eq!(expr.complexity(), 2);
633        let expr: Expr = parse_quote! { w && x || y && z };
634        assert_eq!(expr.complexity(), 3);
635    }
636
637    #[test]
638    fn logical_boolean_operators_not_operator() {
639        let expr: Expr = parse_quote! { !a && !b };
640        assert_eq!(expr.complexity(), 1);
641        let expr: Expr = parse_quote! { a && !(b && c) };
642        assert_eq!(expr.complexity(), 2);
643        let expr: Expr = parse_quote! { !(a || b) && !(c || d) };
644        assert_eq!(expr.complexity(), 3);
645    }
646}