-
Notifications
You must be signed in to change notification settings - Fork 2k
Expand file tree
/
Copy pathcognitive_complexity.rs
More file actions
170 lines (157 loc) · 5.71 KB
/
cognitive_complexity.rs
File metadata and controls
170 lines (157 loc) · 5.71 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::res::MaybeDef;
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use clippy_utils::visitors::for_each_expr_without_closures;
use clippy_utils::{LimitStack, get_async_fn_body, sym};
use core::ops::ControlFlow;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Attribute, Body, Expr, ExprKind, FnDecl};
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::Span;
use rustc_span::def_id::LocalDefId;
declare_clippy_lint! {
/// ### What it does
/// We used to think it measured how hard a method is to understand.
///
/// ### Why is this bad?
/// Ideally, we would like to be able to measure how hard a function is
/// to understand given its context (what we call its Cognitive Complexity).
/// But that's not what this lint does. See "Known problems"
///
/// ### Known problems
/// The true Cognitive Complexity of a method is not something we can
/// calculate using modern technology. This lint has been left in
/// `restriction` so as to not mislead users into using this lint as a
/// measurement tool.
///
/// For more detailed information, see [rust-clippy#3793](https://github.com/rust-lang/rust-clippy/issues/3793)
///
/// ### Lints to consider instead of this
///
/// * [`excessive_nesting`](https://rust-lang.github.io/rust-clippy/master/index.html#excessive_nesting)
/// * [`too_many_lines`](https://rust-lang.github.io/rust-clippy/master/index.html#too_many_lines)
#[clippy::version = "1.35.0"]
pub COGNITIVE_COMPLEXITY,
restriction,
"functions that should be split up into multiple functions",
@eval_always = true
}
impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
pub struct CognitiveComplexity {
limit: LimitStack,
}
impl CognitiveComplexity {
pub fn new(conf: &'static Conf) -> Self {
Self {
limit: LimitStack::new(conf.cognitive_complexity_threshold),
}
}
}
impl CognitiveComplexity {
fn check<'tcx>(
&self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
expr: &'tcx Expr<'_>,
body_span: Span,
) {
if body_span.from_expansion() {
return;
}
let mut cc = 1u64;
let mut returns = 0u64;
let mut prev_expr: Option<&ExprKind<'tcx>> = None;
let _: Option<!> = for_each_expr_without_closures(expr, |e| {
match e.kind {
ExprKind::If(_, _, _) => {
cc += 1;
},
ExprKind::Match(_, arms, _) => {
if arms.len() > 1 {
cc += 1;
}
cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64;
},
ExprKind::Ret(_) if !matches!(prev_expr, Some(ExprKind::Ret(_))) => {
returns += 1;
},
_ => {},
}
prev_expr = Some(&e.kind);
ControlFlow::Continue(())
});
let ret_ty = cx.typeck_results().node_type(expr.hir_id);
let ret_adjust = if ret_ty.is_diag_item(cx, sym::Result) {
returns
} else {
#[expect(clippy::integer_division)]
(returns / 2)
};
// prevent degenerate cases where unreachable code contains `return` statements
if cc >= ret_adjust {
cc -= ret_adjust;
}
if cc > self.limit.limit() {
let fn_span = match kind {
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
FnKind::Closure => {
let header_span = body_span.with_hi(decl.output.span().lo());
if let Some(range) = header_span.map_range(cx, |_, src, range| {
let mut idxs = src.get(range.clone())?.match_indices('|');
Some(range.start + idxs.next()?.0..range.start + idxs.next()?.0 + 1)
}) {
range.with_ctxt(header_span.ctxt())
} else {
return;
}
},
};
span_lint_and_help(
cx,
COGNITIVE_COMPLEXITY,
fn_span,
format!(
"the function has a cognitive complexity of ({cc}/{})",
self.limit.limit()
),
None,
"you could split it up into multiple smaller functions",
);
}
}
}
impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
decl: &'tcx FnDecl<'_>,
body: &'tcx Body<'_>,
span: Span,
def_id: LocalDefId,
) {
#[allow(deprecated)]
if cx.tcx.get_attrs(def_id, sym::test).next().is_none() {
let expr = if kind.asyncness().is_async() {
match get_async_fn_body(cx.tcx, body) {
Some(b) => b,
None => {
return;
},
}
} else {
body.value
};
self.check(cx, kind, decl, expr, span);
}
}
fn check_attributes(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
self.limit.push_attrs(cx.sess(), attrs, sym::cognitive_complexity);
}
fn check_attributes_post(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
self.limit.pop_attrs(cx.sess(), attrs, sym::cognitive_complexity);
}
}