1use syn::{
6 Arm, Block, Expr, ExprBinary, ExprBlock, ExprForLoop, ExprIf, ExprLoop, ExprMatch, ExprWhile,
7 ImplItem, Item, LocalInit, Pat, Stmt,
8};
9
10#[must_use]
11pub fn cognitive_complexity(block: &Block) -> u32 {
12 score_block(block, 0)
13}
14
15fn score_block(block: &Block, nesting: u32) -> u32 {
16 block
17 .stmts
18 .iter()
19 .map(|stmt| score_stmt(stmt, nesting))
20 .sum()
21}
22
23fn score_stmt(stmt: &Stmt, nesting: u32) -> u32 {
24 match stmt {
25 Stmt::Local(local) => score_local_init(&local.init, nesting),
26 Stmt::Item(item) => match item {
27 Item::Fn(item_fn) => score_block(&item_fn.block, nesting),
28 Item::Mod(item_mod) => item_mod.content.as_ref().map_or(0, |(_, items)| {
29 items
30 .iter()
31 .map(|item| score_nested_item(item, nesting))
32 .sum()
33 }),
34 _ => 0,
35 },
36 Stmt::Expr(expr, _) => score_expr(expr, nesting),
37 Stmt::Macro(_) => 0,
38 }
39}
40
41fn score_nested_item(item: &Item, nesting: u32) -> u32 {
42 match item {
43 Item::Fn(item_fn) => score_block(&item_fn.block, nesting),
44 Item::Impl(item_impl) => item_impl
45 .items
46 .iter()
47 .map(|item| match item {
48 ImplItem::Fn(method) => score_block(&method.block, nesting),
49 _ => 0,
50 })
51 .sum(),
52 Item::Mod(item_mod) => item_mod.content.as_ref().map_or(0, |(_, items)| {
53 items
54 .iter()
55 .map(|item| score_nested_item(item, nesting))
56 .sum()
57 }),
58 _ => 0,
59 }
60}
61
62fn score_local_init(init: &Option<LocalInit>, nesting: u32) -> u32 {
63 init.as_ref().map_or(0, |init| {
64 score_expr(&init.expr, nesting)
65 + init
66 .diverge
67 .as_ref()
68 .map_or(0, |(_, expr)| score_expr(expr, nesting))
69 })
70}
71
72fn score_expr(expr: &Expr, nesting: u32) -> u32 {
73 match expr {
74 Expr::If(expr_if) => score_if(expr_if, nesting),
75 Expr::Match(expr_match) => score_match(expr_match, nesting),
76 Expr::ForLoop(expr_for) => score_for(expr_for, nesting),
77 Expr::While(expr_while) => score_while(expr_while, nesting),
78 Expr::Loop(expr_loop) => score_loop(expr_loop, nesting),
79 Expr::Block(ExprBlock { block, .. }) => score_block(block, nesting),
80 Expr::Binary(expr_binary) => {
81 logical_ops(expr_binary)
82 + score_expr(&expr_binary.left, nesting)
83 + score_expr(&expr_binary.right, nesting)
84 }
85 Expr::Call(expr_call) => {
86 score_expr(&expr_call.func, nesting)
87 + expr_call
88 .args
89 .iter()
90 .map(|argument| score_expr(argument, nesting))
91 .sum::<u32>()
92 }
93 Expr::MethodCall(expr_call) => {
94 score_expr(&expr_call.receiver, nesting)
95 + expr_call
96 .args
97 .iter()
98 .map(|argument| score_expr(argument, nesting))
99 .sum::<u32>()
100 }
101 Expr::Closure(expr_closure) => score_expr(&expr_closure.body, nesting),
102 Expr::Async(expr_async) => score_block(&expr_async.block, nesting),
103 Expr::Await(expr_await) => score_expr(&expr_await.base, nesting),
104 Expr::Try(expr_try) => score_expr(&expr_try.expr, nesting),
105 Expr::TryBlock(expr_try_block) => {
106 1 + nesting + score_block(&expr_try_block.block, nesting + 1)
107 }
108 Expr::Unary(expr_unary) => score_expr(&expr_unary.expr, nesting),
109 Expr::Reference(expr_reference) => score_expr(&expr_reference.expr, nesting),
110 Expr::Return(expr_return) => expr_return
111 .expr
112 .as_ref()
113 .map_or(0, |expr| score_expr(expr, nesting)),
114 Expr::Break(expr_break) => expr_break
115 .expr
116 .as_ref()
117 .map_or(0, |expr| score_expr(expr, nesting)),
118 Expr::Paren(expr_paren) => score_expr(&expr_paren.expr, nesting),
119 Expr::Array(expr_array) => expr_array
120 .elems
121 .iter()
122 .map(|expr| score_expr(expr, nesting))
123 .sum(),
124 Expr::Assign(expr_assign) => {
125 score_expr(&expr_assign.left, nesting) + score_expr(&expr_assign.right, nesting)
126 }
127 Expr::Field(expr_field) => score_expr(&expr_field.base, nesting),
128 Expr::Index(expr_index) => {
129 score_expr(&expr_index.expr, nesting) + score_expr(&expr_index.index, nesting)
130 }
131 Expr::Let(expr_let) => score_expr(&expr_let.expr, nesting),
132 Expr::Macro(_) => 0,
133 Expr::Range(expr_range) => {
134 expr_range
135 .start
136 .as_ref()
137 .map_or(0, |expr| score_expr(expr, nesting))
138 + expr_range
139 .end
140 .as_ref()
141 .map_or(0, |expr| score_expr(expr, nesting))
142 }
143 Expr::Repeat(expr_repeat) => {
144 score_expr(&expr_repeat.expr, nesting) + score_expr(&expr_repeat.len, nesting)
145 }
146 Expr::Struct(expr_struct) => {
147 expr_struct
148 .fields
149 .iter()
150 .map(|field| score_expr(&field.expr, nesting))
151 .sum::<u32>()
152 + expr_struct
153 .rest
154 .as_ref()
155 .map_or(0, |expr| score_expr(expr, nesting))
156 }
157 Expr::Tuple(expr_tuple) => expr_tuple
158 .elems
159 .iter()
160 .map(|expr| score_expr(expr, nesting))
161 .sum(),
162 Expr::Unsafe(expr_unsafe) => score_block(&expr_unsafe.block, nesting),
163 Expr::Yield(expr_yield) => expr_yield
164 .expr
165 .as_ref()
166 .map_or(0, |expr| score_expr(expr, nesting)),
167 _ => 0,
168 }
169}
170
171fn score_if(expr_if: &ExprIf, nesting: u32) -> u32 {
172 let mut score = 1
173 + nesting
174 + logical_expr_score(&expr_if.cond)
175 + score_block(&expr_if.then_branch, nesting + 1);
176 if let Some((_, else_branch)) = &expr_if.else_branch {
177 score += match else_branch.as_ref() {
178 Expr::If(else_if) => score_if(else_if, nesting),
179 other => score_expr(other, nesting + 1),
180 };
181 }
182 score
183}
184
185fn score_match(expr_match: &ExprMatch, nesting: u32) -> u32 {
186 1 + nesting
187 + score_expr(&expr_match.expr, nesting)
188 + expr_match
189 .arms
190 .iter()
191 .map(|arm| score_arm(arm, nesting + 1))
192 .sum::<u32>()
193}
194
195fn score_arm(arm: &Arm, nesting: u32) -> u32 {
196 arm.guard.as_ref().map_or(0, |(_, expr)| {
197 logical_expr_score(expr) + score_expr(expr, nesting)
198 }) + score_expr(&arm.body, nesting)
199}
200
201fn score_for(expr_for: &ExprForLoop, nesting: u32) -> u32 {
202 1 + nesting
203 + score_expr(&expr_for.expr, nesting)
204 + pattern_complexity(&expr_for.pat)
205 + score_block(&expr_for.body, nesting + 1)
206}
207
208fn score_while(expr_while: &ExprWhile, nesting: u32) -> u32 {
209 1 + nesting
210 + logical_expr_score(&expr_while.cond)
211 + score_expr(&expr_while.cond, nesting)
212 + score_block(&expr_while.body, nesting + 1)
213}
214
215fn score_loop(expr_loop: &ExprLoop, nesting: u32) -> u32 {
216 1 + nesting + score_block(&expr_loop.body, nesting + 1)
217}
218
219fn pattern_complexity(pattern: &Pat) -> u32 {
220 match pattern {
221 Pat::Or(pattern_or) => pattern_or.cases.len().saturating_sub(1) as u32,
222 _ => 0,
223 }
224}
225
226fn logical_expr_score(expr: &Expr) -> u32 {
227 match expr {
228 Expr::Binary(binary) => {
229 let current = logical_ops(binary);
230 current + logical_expr_score(&binary.left) + logical_expr_score(&binary.right)
231 }
232 Expr::Paren(paren) => logical_expr_score(&paren.expr),
233 Expr::Group(group) => logical_expr_score(&group.expr),
234 _ => 0,
235 }
236}
237
238fn logical_ops(expr_binary: &ExprBinary) -> u32 {
239 if matches!(expr_binary.op, syn::BinOp::And(_) | syn::BinOp::Or(_)) {
240 1
241 } else {
242 0
243 }
244}