Skip to content

Commit a6fb78b

Browse files
Add min and max implementation for Cyclomatic (#699)
* Add cyclomatic_sum field, fix issues related to this change * Add implementation for min and max in cyclomatic metric
1 parent a507c7d commit a6fb78b

File tree

4 files changed

+146
-22
lines changed

4 files changed

+146
-22
lines changed

rust-code-analysis-web/src/web/server.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -631,7 +631,7 @@ mod tests {
631631
"spaces": {"kind": "unit",
632632
"start_line": 1,
633633
"end_line": 4,
634-
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
634+
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min":1.0, "max":1.0},
635635
"cognitive": {"sum": 0.0, "average": 0.0},
636636
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
637637
"nexits": {"sum": 0.0, "average": 0.0},
@@ -658,7 +658,7 @@ mod tests {
658658
"spaces": [{"kind": "function",
659659
"start_line": 3,
660660
"end_line": 4,
661-
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0},
661+
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0, "min":1.0, "max":1.0},
662662
"cognitive": {"sum": 0.0, "average": 0.0},
663663
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
664664
"nexits": {"sum": 0.0, "average": 0.0},
@@ -711,7 +711,7 @@ mod tests {
711711
"spaces": {"kind": "unit",
712712
"start_line": 1,
713713
"end_line": 2,
714-
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
714+
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min":1.0, "max":1.0},
715715
"cognitive": {"sum": 0.0, "average": 0.0},
716716
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
717717
"nexits": {"sum": 0.0, "average": 0.0},
@@ -760,7 +760,7 @@ mod tests {
760760
"spaces": {"kind": "unit",
761761
"start_line": 1,
762762
"end_line": 2,
763-
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
763+
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min": 1.0,"max": 1.0},
764764
"cognitive": {"sum": 0.0, "average": 0.0},
765765
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
766766
"nexits": {"sum": 0.0, "average": 0.0},
@@ -787,7 +787,7 @@ mod tests {
787787
"spaces": [{"kind": "function",
788788
"start_line": 1,
789789
"end_line": 2,
790-
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0},
790+
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0, "min": 1.0,"max": 1.0},
791791
"cognitive": {"sum": 0.0, "average": 0.0},
792792
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
793793
"nexits": {"sum": 0.0, "average": 0.0},

src/metrics/cyclomatic.rs

Lines changed: 134 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -8,15 +8,21 @@ use crate::*;
88
/// The `Cyclomatic` metric.
99
#[derive(Debug, Clone)]
1010
pub struct Stats {
11+
cyclomatic_sum: f64,
1112
cyclomatic: f64,
1213
n: usize,
14+
cyclomatic_max: f64,
15+
cyclomatic_min: f64,
1316
}
1417

1518
impl Default for Stats {
1619
fn default() -> Self {
1720
Self {
21+
cyclomatic_sum: 0.,
1822
cyclomatic: 1.,
1923
n: 1,
24+
cyclomatic_max: 0.,
25+
cyclomatic_min: f64::MAX,
2026
}
2127
}
2228
}
@@ -27,8 +33,10 @@ impl Serialize for Stats {
2733
S: Serializer,
2834
{
2935
let mut st = serializer.serialize_struct("cyclomatic", 2)?;
30-
st.serialize_field("sum", &self.cyclomatic())?;
36+
st.serialize_field("sum", &self.cyclomatic_sum())?;
3137
st.serialize_field("average", &self.cyclomatic_average())?;
38+
st.serialize_field("min", &self.cyclomatic_min())?;
39+
st.serialize_field("max", &self.cyclomatic_max())?;
3240
st.end()
3341
}
3442
}
@@ -37,31 +45,55 @@ impl fmt::Display for Stats {
3745
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
3846
write!(
3947
f,
40-
"sum: {}, average: {}",
41-
self.cyclomatic(),
42-
self.cyclomatic_average()
48+
"sum: {}, average: {}, min: {}, max: {}",
49+
self.cyclomatic_sum(),
50+
self.cyclomatic_average(),
51+
self.cyclomatic_min(),
52+
self.cyclomatic_max()
4353
)
4454
}
4555
}
4656

4757
impl Stats {
4858
/// Merges a second `Cyclomatic` metric into the first one
4959
pub fn merge(&mut self, other: &Stats) {
50-
self.cyclomatic += other.cyclomatic;
60+
//Calculate minimum and maximum values
61+
self.cyclomatic_max = self.cyclomatic_max.max(other.cyclomatic_max);
62+
self.cyclomatic_min = self.cyclomatic_min.min(other.cyclomatic_min);
63+
64+
self.cyclomatic_sum += other.cyclomatic_sum;
5165
self.n += other.n;
5266
}
5367

5468
/// Returns the `Cyclomatic` metric value
5569
pub fn cyclomatic(&self) -> f64 {
5670
self.cyclomatic
5771
}
72+
/// Returns the sum
73+
pub fn cyclomatic_sum(&self) -> f64 {
74+
self.cyclomatic_sum
75+
}
5876

5977
/// Returns the `Cyclomatic` metric average value
6078
///
6179
/// This value is computed dividing the `Cyclomatic` value for the
6280
/// number of spaces.
6381
pub fn cyclomatic_average(&self) -> f64 {
64-
self.cyclomatic() / self.n as f64
82+
self.cyclomatic_sum() / self.n as f64
83+
}
84+
/// Returns the `Cyclomatic` maximum value
85+
pub fn cyclomatic_max(&self) -> f64 {
86+
self.cyclomatic_max
87+
}
88+
/// Returns the `Cyclomatic` minimum value
89+
pub fn cyclomatic_min(&self) -> f64 {
90+
self.cyclomatic_min
91+
}
92+
/// Last step for computing minimum and maximum value and update cyclomatic_sum
93+
pub fn compute_minmax(&mut self) {
94+
self.cyclomatic_max = self.cyclomatic_max.max(self.cyclomatic);
95+
self.cyclomatic_min = self.cyclomatic_min.min(self.cyclomatic);
96+
self.cyclomatic_sum += self.cyclomatic;
6597
}
6698
}
6799

@@ -190,9 +222,11 @@ mod tests {
190222
"foo.py",
191223
PythonParser,
192224
cyclomatic,
193-
[(cyclomatic, 6, usize)],
225+
[(cyclomatic_sum, 6, usize)],
194226
[
195-
(cyclomatic_average, 3.0) // nspace = 2 (func and unit)
227+
(cyclomatic_average, 3.0), // nspace = 2 (func and unit)
228+
(cyclomatic_max, 5.0),
229+
(cyclomatic_min, 1.0)
196230
]
197231
);
198232
}
@@ -207,9 +241,11 @@ mod tests {
207241
"foo.py",
208242
PythonParser,
209243
cyclomatic,
210-
[(cyclomatic, 4, usize)],
244+
[(cyclomatic_sum, 4, usize)],
211245
[
212-
(cyclomatic_average, 2.0) // nspace = 2 (func and unit)
246+
(cyclomatic_average, 2.0), // nspace = 2 (func and unit)
247+
(cyclomatic_max, 3.0),
248+
(cyclomatic_min, 1.0)
213249
]
214250
);
215251
}
@@ -228,9 +264,11 @@ mod tests {
228264
"foo.rs",
229265
RustParser,
230266
cyclomatic,
231-
[(cyclomatic, 5, usize)],
267+
[(cyclomatic_sum, 5, usize)],
232268
[
233-
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
269+
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
270+
(cyclomatic_max, 4.0),
271+
(cyclomatic_min, 1.0)
234272
]
235273
);
236274
}
@@ -257,9 +295,11 @@ mod tests {
257295
"foo.c",
258296
CppParser,
259297
cyclomatic,
260-
[(cyclomatic, 5, usize)],
298+
[(cyclomatic_sum, 5, usize)],
261299
[
262-
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
300+
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
301+
(cyclomatic_max, 4.0),
302+
(cyclomatic_min, 1.0)
263303
]
264304
);
265305
}
@@ -282,9 +322,87 @@ mod tests {
282322
"foo.c",
283323
CppParser,
284324
cyclomatic,
285-
[(cyclomatic, 5, usize)],
325+
[(cyclomatic_sum, 5, usize)],
326+
[
327+
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
328+
(cyclomatic_max, 4.0),
329+
(cyclomatic_min, 1.0)
330+
]
331+
);
332+
}
333+
#[test]
334+
fn c_unit_before() {
335+
check_metrics!(
336+
"
337+
int a=42;
338+
if(a==42) //+2(+1 unit space)
339+
{
340+
341+
}
342+
if(a==34) //+1
343+
{
344+
345+
}
346+
int sumOfPrimes(int max) { // +1
347+
int total = 0;
348+
OUT: for (int i = 1; i <= max; ++i) { // +1
349+
for (int j = 2; j < i; ++j) { // +1
350+
if (i % j == 0) { // +1
351+
continue OUT;
352+
}
353+
}
354+
total += i;
355+
}
356+
return total;
357+
}",
358+
"foo.c",
359+
CppParser,
360+
cyclomatic,
361+
[(cyclomatic_sum, 7, usize)],
362+
[
363+
(cyclomatic_average, 3.5), // nspace = 2 (func and unit)
364+
(cyclomatic_max, 4.0),
365+
(cyclomatic_min, 3.0)
366+
]
367+
);
368+
}
369+
/// Test to handle the case of min and max when merge happen before the final value of one module are setted.
370+
/// In this case the min value should be 3 because the unit space has 2 branches and a complexity of 3
371+
/// while the function sumOfPrimes has a complexity of 4.
372+
#[test]
373+
fn c_unit_after() {
374+
check_metrics!(
375+
"
376+
int sumOfPrimes(int max) { // +1
377+
int total = 0;
378+
OUT: for (int i = 1; i <= max; ++i) { // +1
379+
for (int j = 2; j < i; ++j) { // +1
380+
if (i % j == 0) { // +1
381+
continue OUT;
382+
}
383+
}
384+
total += i;
385+
}
386+
return total;
387+
}
388+
389+
int a=42;
390+
if(a==42) //+2(+1 unit space)
391+
{
392+
393+
}
394+
if(a==34) //+1
395+
{
396+
397+
}",
398+
"foo.c",
399+
CppParser,
400+
cyclomatic,
401+
[(cyclomatic_sum, 7, usize)],
286402
[
287-
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
403+
(cyclomatic_average, 3.5), // nspace = 2 (func and unit)
404+
(cyclomatic_max, 4.0),
405+
(cyclomatic_min, 3.0)
288406
]
289407
);
290408
}

src/metrics/mi.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -96,7 +96,7 @@ where
9696
stats.halstead_length = halstead.length();
9797
stats.halstead_vocabulary = halstead.vocabulary();
9898
stats.halstead_volume = halstead.volume();
99-
stats.cyclomatic = cyclomatic.cyclomatic();
99+
stats.cyclomatic = cyclomatic.cyclomatic_sum();
100100
stats.sloc = loc.sloc();
101101
stats.comments_percentage = loc.cloc() / stats.sloc;
102102
}

src/spaces.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -195,6 +195,10 @@ fn compute_averages(state: &mut State) {
195195
.nargs
196196
.finalize(nom_functions, nom_closures);
197197
}
198+
#[inline(always)]
199+
fn compute_minmax(state: &mut State) {
200+
state.space.metrics.cyclomatic.compute_minmax();
201+
}
198202

199203
fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
200204
if state_stack.is_empty() {
@@ -203,11 +207,13 @@ fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
203207
for _ in 0..diff_level {
204208
if state_stack.len() == 1 {
205209
let mut last_state = state_stack.last_mut().unwrap();
210+
compute_minmax(&mut last_state);
206211
compute_halstead_and_mi::<T>(&mut last_state);
207212
compute_averages(&mut last_state);
208213
break;
209214
} else {
210215
let mut state = state_stack.pop().unwrap();
216+
compute_minmax(&mut state);
211217
compute_halstead_and_mi::<T>(&mut state);
212218
compute_averages(&mut state);
213219

0 commit comments

Comments
 (0)