]> git.lizzy.rs Git - rust.git/blob - clippy_lints/src/cognitive_complexity.rs
Auto merge of #4560 - rust-lang:must-use-pure, r=phansch
[rust.git] / clippy_lints / src / cognitive_complexity.rs
1 //! calculate cognitive complexity and warn about overly complex functions
2
3 use rustc::hir::intravisit::{walk_expr, NestedVisitorMap, Visitor};
4 use rustc::hir::*;
5 use rustc::lint::{LateContext, LateLintPass, LintArray, LintContext, LintPass};
6 use rustc::{declare_tool_lint, impl_lint_pass};
7 use syntax::ast::Attribute;
8 use syntax::source_map::Span;
9
10 use crate::utils::{match_type, paths, span_help_and_lint, LimitStack};
11
12 declare_clippy_lint! {
13     /// **What it does:** Checks for methods with high cognitive complexity.
14     ///
15     /// **Why is this bad?** Methods of high cognitive complexity tend to be hard to
16     /// both read and maintain. Also LLVM will tend to optimize small methods better.
17     ///
18     /// **Known problems:** Sometimes it's hard to find a way to reduce the
19     /// complexity.
20     ///
21     /// **Example:** No. You'll see it when you get the warning.
22     pub COGNITIVE_COMPLEXITY,
23     complexity,
24     "functions that should be split up into multiple functions"
25 }
26
27 pub struct CognitiveComplexity {
28     limit: LimitStack,
29 }
30
31 impl CognitiveComplexity {
32     #[must_use]
33     pub fn new(limit: u64) -> Self {
34         Self {
35             limit: LimitStack::new(limit),
36         }
37     }
38 }
39
40 impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
41
42 impl CognitiveComplexity {
43     fn check<'a, 'tcx>(&mut self, cx: &'a LateContext<'a, 'tcx>, body: &'tcx Body, span: Span) {
44         if span.from_expansion() {
45             return;
46         }
47
48         let expr = &body.value;
49
50         let mut helper = CCHelper { cc: 1, returns: 0 };
51         helper.visit_expr(expr);
52         let CCHelper { cc, returns } = helper;
53         let ret_ty = cx.tables.node_type(expr.hir_id);
54         let ret_adjust = if match_type(cx, ret_ty, &paths::RESULT) {
55             returns
56         } else {
57             #[allow(clippy::integer_division)]
58             (returns / 2)
59         };
60
61         let mut rust_cc = cc;
62         // prevent degenerate cases where unreachable code contains `return` statements
63         if rust_cc >= ret_adjust {
64             rust_cc -= ret_adjust;
65         }
66         if rust_cc > self.limit.limit() {
67             span_help_and_lint(
68                 cx,
69                 COGNITIVE_COMPLEXITY,
70                 span,
71                 &format!(
72                     "the function has a cognitive complexity of ({}/{})",
73                     rust_cc,
74                     self.limit.limit()
75                 ),
76                 "you could split it up into multiple smaller functions",
77             );
78         }
79     }
80 }
81
82 impl<'a, 'tcx> LateLintPass<'a, 'tcx> for CognitiveComplexity {
83     fn check_fn(
84         &mut self,
85         cx: &LateContext<'a, 'tcx>,
86         _: intravisit::FnKind<'tcx>,
87         _: &'tcx FnDecl,
88         body: &'tcx Body,
89         span: Span,
90         hir_id: HirId,
91     ) {
92         let def_id = cx.tcx.hir().local_def_id(hir_id);
93         if !cx.tcx.has_attr(def_id, sym!(test)) {
94             self.check(cx, body, span);
95         }
96     }
97
98     fn enter_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
99         self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity");
100     }
101     fn exit_lint_attrs(&mut self, cx: &LateContext<'a, 'tcx>, attrs: &'tcx [Attribute]) {
102         self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
103     }
104 }
105
106 struct CCHelper {
107     cc: u64,
108     returns: u64,
109 }
110
111 impl<'tcx> Visitor<'tcx> for CCHelper {
112     fn visit_expr(&mut self, e: &'tcx Expr) {
113         walk_expr(self, e);
114         match e.kind {
115             ExprKind::Match(_, ref arms, _) => {
116                 if arms.len() > 1 {
117                     self.cc += 1;
118                 }
119                 self.cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
120             },
121             ExprKind::Ret(_) => self.returns += 1,
122             _ => {},
123         }
124     }
125     fn nested_visit_map<'this>(&'this mut self) -> NestedVisitorMap<'this, 'tcx> {
126         NestedVisitorMap::None
127     }
128 }