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