Skip to content

Commit 2f6af3e

Browse files
giovannitangrediLuni-4
authored andcommitted
Refactor cognitive complexity
* Convert macros into functions * Clean up code * Add a map to compute the nesting for each node
1 parent 9618bca commit 2f6af3e

File tree

2 files changed

+137
-131
lines changed

2 files changed

+137
-131
lines changed

src/metrics/cognitive.rs

Lines changed: 131 additions & 129 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use fxhash::FxHashMap;
12
use serde::ser::{SerializeStruct, Serializer};
23
use serde::Serialize;
34
use std::fmt;
@@ -123,65 +124,28 @@ pub trait Cognitive
123124
where
124125
Self: Checker,
125126
{
126-
fn compute(_node: &Node, _stats: &mut Stats) {}
127+
fn compute(
128+
_node: &Node,
129+
_stats: &mut Stats,
130+
_nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
131+
) {
132+
}
127133
}
128134

129-
macro_rules! compute_booleans {
130-
($node: ident, $stats: ident, $( $typs: pat_param )|*) => {
131-
let mut cursor = $node.object().walk();
132-
for child in $node.object().children(&mut cursor) {
133-
if let $( $typs )|* = child.kind_id().into() {
134-
$stats.structural = $stats
135-
.boolean_seq
136-
.eval_based_on_prev(child.kind_id(), $stats.structural);
137-
}
135+
fn compute_booleans<T: std::cmp::PartialEq + std::convert::From<u16>>(
136+
node: &Node,
137+
stats: &mut Stats,
138+
typs1: T,
139+
typs2: T,
140+
) {
141+
let mut cursor = node.object().walk();
142+
for child in node.object().children(&mut cursor) {
143+
if typs1 == child.kind_id().into() || typs2 == child.kind_id().into() {
144+
stats.structural = stats
145+
.boolean_seq
146+
.eval_based_on_prev(child.kind_id(), stats.structural)
138147
}
139-
};
140-
}
141-
142-
macro_rules! nesting_levels {
143-
($node: ident, $stats: ident, [$nest_func: pat_param => $nest_func_stop: pat_param],
144-
[$lambdas: pat => $( $lambdas_stop: pat_param )|*],
145-
[$( $nest_level: pat_param )|* => $( $nest_level_stop: pat_param )|*]) => {
146-
// Find the depth of a function (the most external function is
147-
// not considered)
148-
$stats.nesting = count_specific_ancestors!($node, $nest_func, $nest_func_stop).max(1) - 1;
149-
150-
// Find the depth of a lambda
151-
let lambda_depth = count_specific_ancestors!($node, $lambdas, $( $lambdas_stop )|*);
152-
153-
// Find the nesting operator level
154-
$stats.nesting += lambda_depth
155-
+ count_specific_ancestors!(
156-
$node,
157-
$( $nest_level )|*,
158-
$( $nest_level_stop)|*
159-
);
160-
161-
// Reset the boolean sequence
162-
$stats.boolean_seq.reset();
163-
164-
increment($stats);
165-
};
166-
($node: ident, $stats: ident,
167-
[$lambdas: pat_param => $( $lambdas_stop: pat_param )|*],
168-
[$( $nest_level: pat_param )|* => $( $nest_level_stop: pat_param )|*]) => {
169-
// Find the depth of a lambda
170-
let lambda_depth = count_specific_ancestors!($node, $lambdas, $( $lambdas_stop )|*);
171-
172-
// Find the nesting operator level
173-
$stats.nesting = lambda_depth
174-
+ count_specific_ancestors!(
175-
$node,
176-
$( $nest_level )|*,
177-
$( $nest_level_stop)|*
178-
);
179-
180-
// Reset the boolean sequence
181-
$stats.boolean_seq.reset();
182-
183-
increment($stats);
184-
};
148+
}
185149
}
186150

187151
#[derive(Debug, Default, Clone)]
@@ -228,18 +192,59 @@ fn increment_by_one(stats: &mut Stats) {
228192
stats.structural += 1;
229193
}
230194

195+
fn get_nesting_from_map(
196+
node: &Node,
197+
nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
198+
) -> (usize, usize, usize) {
199+
if let Some(parent) = node.object().parent() {
200+
if let Some(n) = nesting_map.get(&parent.id()) {
201+
*n
202+
} else {
203+
(0, 0, 0)
204+
}
205+
} else {
206+
(0, 0, 0)
207+
}
208+
}
209+
210+
fn increment_function_depth<T: std::cmp::PartialEq + std::convert::From<u16>>(
211+
depth: &mut usize,
212+
node: &Node,
213+
stop: T,
214+
) {
215+
// Increase depth function nesting if needed
216+
let mut child = node.object();
217+
while let Some(parent) = child.parent() {
218+
if stop == parent.kind_id().into() {
219+
*depth += 1;
220+
break;
221+
}
222+
child = parent;
223+
}
224+
}
225+
226+
#[inline(always)]
227+
fn increase_nesting(stats: &mut Stats, nesting: &mut usize, depth: usize, lambda: usize) {
228+
stats.nesting = *nesting + depth + lambda;
229+
increment(stats);
230+
*nesting += 1;
231+
stats.boolean_seq.reset();
232+
}
233+
231234
impl Cognitive for PythonCode {
232-
fn compute(node: &Node, stats: &mut Stats) {
235+
fn compute(
236+
node: &Node,
237+
stats: &mut Stats,
238+
nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
239+
) {
233240
use Python::*;
234241

242+
// Get nesting of the parent
243+
let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
244+
235245
match node.object().kind_id().into() {
236246
IfStatement | ForStatement | WhileStatement | ConditionalExpression => {
237-
nesting_levels!(
238-
node, stats,
239-
[FunctionDefinition => Module],
240-
[Lambda => FunctionDefinition | Module],
241-
[IfStatement | ForStatement | WhileStatement | ExceptClause => FunctionDefinition]
242-
);
247+
increase_nesting(stats, &mut nesting, depth, lambda);
243248
}
244249
ElifClause => {
245250
// No nesting increment for them because their cost has already
@@ -254,6 +259,7 @@ impl Cognitive for PythonCode {
254259
increment_by_one(stats);
255260
}
256261
ExceptClause => {
262+
nesting += 1;
257263
increment(stats);
258264
}
259265
ExpressionList | ExpressionStatement | Tuple => {
@@ -270,38 +276,46 @@ impl Cognitive for PythonCode {
270276
ExpressionList | IfStatement | ForStatement | WhileStatement
271277
);
272278
}
273-
compute_booleans!(node, stats, And | Or);
279+
compute_booleans::<language_python::Python>(node, stats, And, Or);
280+
}
281+
Lambda => {
282+
// Increase lambda nesting
283+
lambda += 1;
284+
}
285+
FunctionDefinition => {
286+
// Increase depth function nesting if needed
287+
increment_function_depth::<language_python::Python>(
288+
&mut depth,
289+
node,
290+
FunctionDefinition,
291+
);
274292
}
275293
_ => {}
276294
}
295+
// Add node to nesting map
296+
nesting_map.insert(node.object().id(), (nesting, depth, lambda));
277297
}
278298
}
279299

280300
impl Cognitive for RustCode {
281-
fn compute(node: &Node, stats: &mut Stats) {
301+
fn compute(
302+
node: &Node,
303+
stats: &mut Stats,
304+
nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
305+
) {
282306
use Rust::*;
283-
284307
//TODO: Implement macros
308+
let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
285309

286310
match node.object().kind_id().into() {
287311
IfExpression | IfLetExpression => {
288312
// Check if a node is not an else-if
289313
if !Self::is_else_if(node) {
290-
nesting_levels!(
291-
node, stats,
292-
[FunctionItem => SourceFile],
293-
[ClosureExpression => SourceFile],
294-
[IfExpression | IfLetExpression | ForExpression | WhileExpression | MatchExpression => FunctionItem]
295-
);
314+
increase_nesting(stats,&mut nesting, depth, lambda);
296315
}
297316
}
298317
ForExpression | WhileExpression | MatchExpression => {
299-
nesting_levels!(
300-
node, stats,
301-
[FunctionItem => SourceFile],
302-
[ClosureExpression => SourceFile],
303-
[IfExpression | IfLetExpression | ForExpression | WhileExpression | MatchExpression => FunctionItem]
304-
);
318+
increase_nesting(stats,&mut nesting, depth, lambda);
305319
}
306320
Else /*else-if also */ => {
307321
increment_by_one(stats);
@@ -317,45 +331,41 @@ impl Cognitive for RustCode {
317331
stats.boolean_seq.not_operator(node.object().kind_id());
318332
}
319333
BinaryExpression => {
320-
compute_booleans!(node, stats, AMPAMP | PIPEPIPE);
334+
compute_booleans::<language_rust::Rust>(node, stats, AMPAMP, PIPEPIPE);
335+
}
336+
FunctionItem => {
337+
nesting = 0;
338+
// Increase depth function nesting if needed
339+
increment_function_depth::<language_rust::Rust>(&mut depth, node, FunctionItem);
340+
}
341+
ClosureExpression => {
342+
lambda += 1;
321343
}
322344
_ => {}
323345
}
346+
nesting_map.insert(node.object().id(), (nesting, depth, lambda));
324347
}
325348
}
326349

327350
impl Cognitive for CppCode {
328-
fn compute(node: &Node, stats: &mut Stats) {
351+
fn compute(
352+
node: &Node,
353+
stats: &mut Stats,
354+
nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>,
355+
) {
329356
use Cpp::*;
330357

331358
//TODO: Implement macros
359+
let (mut nesting, depth, mut lambda) = get_nesting_from_map(node, nesting_map);
332360

333361
match node.object().kind_id().into() {
334362
IfStatement => {
335363
if !Self::is_else_if(node) {
336-
nesting_levels!(
337-
node, stats,
338-
[LambdaExpression => TranslationUnit],
339-
[IfStatement
340-
| ForStatement
341-
| WhileStatement
342-
| DoStatement
343-
| SwitchStatement
344-
| CatchClause => FunctionDefinition]
345-
);
364+
increase_nesting(stats,&mut nesting, depth, lambda);
346365
}
347366
}
348367
ForStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause => {
349-
nesting_levels!(
350-
node, stats,
351-
[LambdaExpression => TranslationUnit],
352-
[IfStatement
353-
| ForStatement
354-
| WhileStatement
355-
| DoStatement
356-
| SwitchStatement
357-
| CatchClause => FunctionDefinition]
358-
);
368+
increase_nesting(stats,&mut nesting, depth, lambda);
359369
}
360370
GotoStatement | Else /* else-if also */ => {
361371
increment_by_one(stats);
@@ -364,50 +374,31 @@ impl Cognitive for CppCode {
364374
stats.boolean_seq.not_operator(node.object().kind_id());
365375
}
366376
BinaryExpression2 => {
367-
compute_booleans!(node, stats, AMPAMP | PIPEPIPE);
377+
compute_booleans::<language_cpp::Cpp>(node, stats, AMPAMP, PIPEPIPE);
378+
}
379+
LambdaExpression => {
380+
lambda += 1;
368381
}
369382
_ => {}
370383
}
384+
nesting_map.insert(node.object().id(), (nesting, depth, lambda));
371385
}
372386
}
373387

374388
macro_rules! js_cognitive {
375389
($lang:ident) => {
376-
fn compute(node: &Node, stats: &mut Stats) {
390+
fn compute(node: &Node, stats: &mut Stats, nesting_map: &mut FxHashMap<usize, (usize, usize, usize)>) {
377391
use $lang::*;
392+
let (mut nesting, mut depth, mut lambda) = get_nesting_from_map(node, nesting_map);
378393

379394
match node.object().kind_id().into() {
380395
IfStatement => {
381396
if !Self::is_else_if(&node) {
382-
nesting_levels!(
383-
node, stats,
384-
[FunctionDeclaration => Program],
385-
[ArrowFunction => FunctionDeclaration | Program],
386-
[IfStatement
387-
| ForStatement
388-
| ForInStatement
389-
| WhileStatement
390-
| DoStatement
391-
| SwitchStatement
392-
| CatchClause
393-
| TernaryExpression => FunctionDeclaration]
394-
);
397+
increase_nesting(stats,&mut nesting, depth, lambda);
395398
}
396399
}
397400
ForStatement | ForInStatement | WhileStatement | DoStatement | SwitchStatement | CatchClause | TernaryExpression => {
398-
nesting_levels!(
399-
node, stats,
400-
[FunctionDeclaration => Program],
401-
[ArrowFunction => FunctionDeclaration | Program],
402-
[IfStatement
403-
| ForStatement
404-
| ForInStatement
405-
| WhileStatement
406-
| DoStatement
407-
| SwitchStatement
408-
| CatchClause
409-
| TernaryExpression => FunctionDeclaration]
410-
);
401+
increase_nesting(stats,&mut nesting, depth, lambda);
411402
}
412403
Else /* else-if also */ => {
413404
increment_by_one(stats);
@@ -420,10 +411,21 @@ macro_rules! js_cognitive {
420411
stats.boolean_seq.not_operator(node.object().kind_id());
421412
}
422413
BinaryExpression => {
423-
compute_booleans!(node, stats, AMPAMP | PIPEPIPE);
414+
compute_booleans::<$lang>(node, stats, AMPAMP, PIPEPIPE);
415+
}
416+
FunctionDeclaration => {
417+
// Reset lambda nesting at function for JS
418+
nesting = 0;
419+
lambda = 0;
420+
// Increase depth function nesting if needed
421+
increment_function_depth::<$lang>(&mut depth, node, FunctionDeclaration);
422+
}
423+
ArrowFunction => {
424+
lambda += 1;
424425
}
425426
_ => {}
426427
}
428+
nesting_map.insert(node.object().id(), (nesting, depth, lambda));
427429
}
428430
};
429431
}

src/spaces.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
use fxhash::FxHashMap;
12
use serde::Serialize;
23
use std::fmt;
34
use std::path::{Path, PathBuf};
@@ -293,7 +294,10 @@ pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<Func
293294
let mut children = Vec::new();
294295
let mut state_stack: Vec<State> = Vec::new();
295296
let mut last_level = 0;
296-
297+
// Initialize nesting_map used for storing nesting information for cognitive
298+
// Three type of nesting info: conditionals, functions and lambdas
299+
let mut nesting_map = FxHashMap::<usize, (usize, usize, usize)>::default();
300+
nesting_map.insert(node.object().id(), (0, 0, 0));
297301
stack.push((node, 0));
298302

299303
while let Some((node, level)) = stack.pop() {
@@ -321,7 +325,7 @@ pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<Func
321325

322326
if let Some(state) = state_stack.last_mut() {
323327
let last = &mut state.space;
324-
T::Cognitive::compute(&node, &mut last.metrics.cognitive);
328+
T::Cognitive::compute(&node, &mut last.metrics.cognitive, &mut nesting_map);
325329
T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
326330
T::Halstead::compute(&node, code, &mut state.halstead_maps);
327331
T::Loc::compute(&node, &mut last.metrics.loc, func_space, unit);

0 commit comments

Comments
 (0)