rust/clippy_lints/src/cognitive_complexity.rs

160 lines
5.0 KiB
Rust
Raw Normal View History

use clippy_config::Conf;
use clippy_utils::diagnostics::span_lint_and_help;
use clippy_utils::source::{IntoSpan, SpanRangeExt};
use clippy_utils::ty::is_type_diagnostic_item;
use clippy_utils::visitors::for_each_expr_without_closures;
2024-09-22 13:42:10 -05:00
use clippy_utils::{LimitStack, get_async_fn_body, is_async_fn};
use core::ops::ControlFlow;
2020-02-29 21:23:33 -06:00
use rustc_ast::ast::Attribute;
use rustc_hir::intravisit::FnKind;
use rustc_hir::{Body, Expr, ExprKind, FnDecl};
2020-01-12 00:08:41 -06:00
use rustc_lint::{LateContext, LateLintPass, LintContext};
use rustc_session::impl_lint_pass;
use rustc_span::def_id::LocalDefId;
2024-09-22 13:42:10 -05:00
use rustc_span::{Span, sym};
2018-03-28 08:24:26 -05:00
declare_clippy_lint! {
/// ### What it does
/// Checks for methods with high cognitive complexity.
///
/// ### Why is this bad?
/// Methods of high cognitive complexity tend to be hard to
/// both read and maintain. Also LLVM will tend to optimize small methods better.
///
/// ### Known problems
/// Sometimes it's hard to find a way to reduce the
/// complexity.
///
/// ### Example
/// You'll see it when you get the warning.
#[clippy::version = "1.35.0"]
pub COGNITIVE_COMPLEXITY,
2020-04-06 17:46:40 -05:00
nursery,
"functions that should be split up into multiple functions"
}
pub struct CognitiveComplexity {
limit: LimitStack,
}
impl CognitiveComplexity {
pub fn new(conf: &'static Conf) -> Self {
2017-09-05 04:33:04 -05:00
Self {
limit: LimitStack::new(conf.cognitive_complexity_threshold),
2017-09-05 04:33:04 -05:00
}
}
}
2019-04-08 15:43:55 -05:00
impl_lint_pass!(CognitiveComplexity => [COGNITIVE_COMPLEXITY]);
impl CognitiveComplexity {
fn check<'tcx>(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
2019-12-29 22:02:10 -06:00
decl: &'tcx FnDecl<'_>,
expr: &'tcx Expr<'_>,
body_span: Span,
) {
if body_span.from_expansion() {
2016-01-03 22:26:12 -06:00
return;
}
let mut cc = 1u64;
let mut returns = 0u64;
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(_) => returns += 1,
_ => {},
}
ControlFlow::Continue(())
});
2019-09-08 05:39:42 -05:00
2020-07-17 03:47:04 -05:00
let ret_ty = cx.typeck_results().node_type(expr.hir_id);
2021-10-02 18:51:01 -05:00
let ret_adjust = if is_type_diagnostic_item(cx, ret_ty, sym::Result) {
2016-04-23 07:30:05 -05:00
returns
} else {
#[expect(clippy::integer_division)]
2019-06-11 11:53:12 -05:00
(returns / 2)
2016-04-23 07:30:05 -05:00
};
2019-09-08 05:39:42 -05:00
// prevent degenerate cases where unreachable code contains `return` statements
if cc >= ret_adjust {
cc -= ret_adjust;
2019-09-08 05:39:42 -05:00
}
if cc > self.limit.limit() {
let fn_span = match kind {
2022-02-13 08:40:08 -06:00
FnKind::ItemFn(ident, _, _) | FnKind::Method(ident, _) => ident.span,
2020-11-27 02:24:42 -06:00
FnKind::Closure => {
let header_span = body_span.with_hi(decl.output.span().lo());
#[expect(clippy::range_plus_one)]
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(
2017-08-11 07:11:46 -05:00
cx,
2019-09-08 05:39:42 -05:00
COGNITIVE_COMPLEXITY,
fn_span,
format!(
"the function has a cognitive complexity of ({cc}/{})",
2019-09-08 05:39:42 -05:00
self.limit.limit()
),
None,
2019-09-08 05:39:42 -05:00
"you could split it up into multiple smaller functions",
2017-08-11 07:11:46 -05:00
);
}
}
}
impl<'tcx> LateLintPass<'tcx> for CognitiveComplexity {
2017-01-13 10:04:56 -06:00
fn check_fn(
&mut self,
cx: &LateContext<'tcx>,
kind: FnKind<'tcx>,
2019-12-29 22:02:10 -06:00
decl: &'tcx FnDecl<'_>,
2019-12-22 08:42:41 -06:00
body: &'tcx Body<'_>,
2017-01-13 10:04:56 -06:00
span: Span,
def_id: LocalDefId,
2017-01-13 10:04:56 -06:00
) {
2023-03-13 13:54:05 -05:00
if !cx.tcx.has_attr(def_id, sym::test) {
let expr = if is_async_fn(kind) {
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, "cognitive_complexity");
}
fn check_attributes_post(&mut self, cx: &LateContext<'tcx>, attrs: &'tcx [Attribute]) {
self.limit.pop_attrs(cx.sess(), attrs, "cognitive_complexity");
}
}