]>
Commit | Line | Data |
---|---|---|
f20569fa XL |
1 | //! calculate cognitive complexity and warn about overly complex functions |
2 | ||
cdc7bbd5 XL |
3 | use clippy_utils::diagnostics::span_lint_and_help; |
4 | use clippy_utils::source::snippet_opt; | |
5 | use clippy_utils::ty::is_type_diagnostic_item; | |
2b03887a | 6 | use clippy_utils::visitors::for_each_expr; |
487cf647 | 7 | use clippy_utils::{get_async_fn_body, is_async_fn, LimitStack}; |
2b03887a | 8 | use core::ops::ControlFlow; |
f20569fa | 9 | use rustc_ast::ast::Attribute; |
2b03887a | 10 | use rustc_hir::intravisit::FnKind; |
487cf647 | 11 | use rustc_hir::{Body, Expr, ExprKind, FnDecl, HirId}; |
f20569fa | 12 | use rustc_lint::{LateContext, LateLintPass, LintContext}; |
f20569fa XL |
13 | use rustc_session::{declare_tool_lint, impl_lint_pass}; |
14 | use rustc_span::source_map::Span; | |
15 | use rustc_span::{sym, BytePos}; | |
16 | ||
f20569fa | 17 | declare_clippy_lint! { |
94222f64 XL |
18 | /// ### What it does |
19 | /// Checks for methods with high cognitive complexity. | |
f20569fa | 20 | /// |
94222f64 XL |
21 | /// ### Why is this bad? |
22 | /// Methods of high cognitive complexity tend to be hard to | |
f20569fa XL |
23 | /// both read and maintain. Also LLVM will tend to optimize small methods better. |
24 | /// | |
94222f64 XL |
25 | /// ### Known problems |
26 | /// Sometimes it's hard to find a way to reduce the | |
f20569fa XL |
27 | /// complexity. |
28 | /// | |
94222f64 | 29 | /// ### Example |
923072b8 | 30 | /// You'll see it when you get the warning. |
a2a8927a | 31 | #[clippy::version = "1.35.0"] |
f20569fa XL |
32 | pub COGNITIVE_COMPLEXITY, |
33 | nursery, | |
34 | "functions that should be split up into multiple functions" | |
35 | } | |
36 | ||
37 | pub struct CognitiveComplexity { | |
38 | limit: LimitStack, | |
39 | } | |
40 | ||
41 | impl CognitiveComplexity { | |
42 | #[must_use] | |
43 | pub fn new(limit: u64) -> Self { | |
44 | Self { | |
45 | limit: LimitStack::new(limit), | |
46 | } | |
47 | } | |
48 | } | |
49 | ||
50 | impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]); | |
51 | ||
52 | impl CognitiveComplexity { | |
923072b8 | 53 | #[expect(clippy::cast_possible_truncation)] |
f20569fa XL |
54 | fn check<'tcx>( |
55 | &mut self, | |
56 | cx: &LateContext<'tcx>, | |
57 | kind: FnKind<'tcx>, | |
58 | decl: &'tcx FnDecl<'_>, | |
487cf647 | 59 | expr: &'tcx Expr<'_>, |
f20569fa XL |
60 | body_span: Span, |
61 | ) { | |
62 | if body_span.from_expansion() { | |
63 | return; | |
64 | } | |
65 | ||
2b03887a FG |
66 | let mut cc = 1u64; |
67 | let mut returns = 0u64; | |
68 | let _: Option<!> = for_each_expr(expr, |e| { | |
69 | match e.kind { | |
70 | ExprKind::If(_, _, _) => { | |
71 | cc += 1; | |
72 | }, | |
73 | ExprKind::Match(_, arms, _) => { | |
74 | if arms.len() > 1 { | |
75 | cc += 1; | |
76 | } | |
77 | cc += arms.iter().filter(|arm| arm.guard.is_some()).count() as u64; | |
78 | }, | |
79 | ExprKind::Ret(_) => returns += 1, | |
80 | _ => {}, | |
81 | } | |
82 | ControlFlow::Continue(()) | |
83 | }); | |
f20569fa | 84 | |
f20569fa | 85 | let ret_ty = cx.typeck_results().node_type(expr.hir_id); |
c295e0f8 | 86 | let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) { |
f20569fa XL |
87 | returns |
88 | } else { | |
923072b8 | 89 | #[expect(clippy::integer_division)] |
f20569fa XL |
90 | (returns / 2) |
91 | }; | |
92 | ||
f20569fa | 93 | // prevent degenerate cases where unreachable code contains `return` statements |
2b03887a FG |
94 | if cc >= ret_adjust { |
95 | cc -= ret_adjust; | |
f20569fa XL |
96 | } |
97 | ||
2b03887a | 98 | if cc > self.limit.limit() { |
f20569fa | 99 | let fn_span = match kind { |
04454e1e | 100 | FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span, |
f20569fa XL |
101 | FnKind::Closure => { |
102 | let header_span = body_span.with_hi(decl.output.span().lo()); | |
103 | let pos = snippet_opt(cx, header_span).and_then(|snip| { | |
104 | let low_offset = snip.find('|')?; | |
105 | let high_offset = 1 + snip.get(low_offset + 1..)?.find('|')?; | |
106 | let low = header_span.lo() + BytePos(low_offset as u32); | |
107 | let high = low + BytePos(high_offset as u32 + 1); | |
108 | ||
109 | Some((low, high)) | |
110 | }); | |
111 | ||
112 | if let Some((low, high)) = pos { | |
c295e0f8 | 113 | Span::new(low, high, header_span.ctxt(), header_span.parent()) |
f20569fa XL |
114 | } else { |
115 | return; | |
116 | } | |
117 | }, | |
118 | }; | |
119 | ||
120 | span_lint_and_help( | |
121 | cx, | |
122 | COGNITIVE_COMPLEXITY, | |
123 | fn_span, | |
124 | &format!( | |
2b03887a | 125 | "the function has a cognitive complexity of ({cc}/{})", |
f20569fa XL |
126 | self.limit.limit() |
127 | ), | |
128 | None, | |
129 | "you could split it up into multiple smaller functions", | |
130 | ); | |
131 | } | |
132 | } | |
133 | } | |
134 | ||
135 | impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity { | |
136 | fn check_fn( | |
137 | &mut self, | |
138 | cx: &LateContext<'tcx>, | |
139 | kind: FnKind<'tcx>, | |
140 | decl: &'tcx FnDecl<'_>, | |
141 | body: &'tcx Body<'_>, | |
142 | span: Span, | |
143 | hir_id: HirId, | |
144 | ) { | |
145 | let def_id = cx.tcx.hir().local_def_id(hir_id); | |
146 | if !cx.tcx.has_attr(def_id.to_def_id(), sym::test) { | |
487cf647 FG |
147 | let expr = if is_async_fn(kind) { |
148 | match get_async_fn_body(cx.tcx, body) { | |
149 | Some(b) => b, | |
150 | None => { | |
151 | return; | |
152 | }, | |
153 | } | |
154 | } else { | |
155 | body.value | |
156 | }; | |
157 | ||
158 | self.check(cx, kind, decl, expr, span); | |
f20569fa XL |
159 | } |
160 | } | |
161 | ||
162 | fn enter_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) { | |
163 | self.limit.push_attrs(cx.sess(), attrs, "cognitive_complexity"); | |
164 | } | |
165 | fn exit_lint_attrs(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) { | |
166 | self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity"); | |
167 | } | |
168 | } |