Skip to content

Commit

Permalink
Add min and max implementation for Cyclomatic (#699)
Browse files Browse the repository at this point in the history
* Add cyclomatic_sum field, fix issues related to this change

* Add implementation for min and max in cyclomatic metric
  • Loading branch information
giovannitangredi authored Nov 25, 2021
1 parent a507c7d commit a6fb78b
Show file tree
Hide file tree
Showing 4 changed files with 146 additions and 22 deletions.
10 changes: 5 additions & 5 deletions rust-code-analysis-web/src/web/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -631,7 +631,7 @@ mod tests {
"spaces": {"kind": "unit",
"start_line": 1,
"end_line": 4,
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min":1.0, "max":1.0},
"cognitive": {"sum": 0.0, "average": 0.0},
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
"nexits": {"sum": 0.0, "average": 0.0},
Expand All @@ -658,7 +658,7 @@ mod tests {
"spaces": [{"kind": "function",
"start_line": 3,
"end_line": 4,
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0},
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0, "min":1.0, "max":1.0},
"cognitive": {"sum": 0.0, "average": 0.0},
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
"nexits": {"sum": 0.0, "average": 0.0},
Expand Down Expand Up @@ -711,7 +711,7 @@ mod tests {
"spaces": {"kind": "unit",
"start_line": 1,
"end_line": 2,
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min":1.0, "max":1.0},
"cognitive": {"sum": 0.0, "average": 0.0},
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
"nexits": {"sum": 0.0, "average": 0.0},
Expand Down Expand Up @@ -760,7 +760,7 @@ mod tests {
"spaces": {"kind": "unit",
"start_line": 1,
"end_line": 2,
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0},
"metrics": {"cyclomatic": {"sum": 2.0, "average": 1.0, "min": 1.0,"max": 1.0},
"cognitive": {"sum": 0.0, "average": 0.0},
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
"nexits": {"sum": 0.0, "average": 0.0},
Expand All @@ -787,7 +787,7 @@ mod tests {
"spaces": [{"kind": "function",
"start_line": 1,
"end_line": 2,
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0},
"metrics": {"cyclomatic": {"sum": 1.0, "average": 1.0, "min": 1.0,"max": 1.0},
"cognitive": {"sum": 0.0, "average": 0.0},
"nargs": {"total_functions": 0.0, "average_functions": 0.0, "total_closures": 0.0, "average_closures": 0.0, "total": 0.0, "average": 0.0},
"nexits": {"sum": 0.0, "average": 0.0},
Expand Down
150 changes: 134 additions & 16 deletions src/metrics/cyclomatic.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,15 +8,21 @@ use crate::*;
/// The `Cyclomatic` metric.
#[derive(Debug, Clone)]
pub struct Stats {
cyclomatic_sum: f64,
cyclomatic: f64,
n: usize,
cyclomatic_max: f64,
cyclomatic_min: f64,
}

impl Default for Stats {
fn default() -> Self {
Self {
cyclomatic_sum: 0.,
cyclomatic: 1.,
n: 1,
cyclomatic_max: 0.,
cyclomatic_min: f64::MAX,
}
}
}
Expand All @@ -27,8 +33,10 @@ impl Serialize for Stats {
S: Serializer,
{
let mut st = serializer.serialize_struct("cyclomatic", 2)?;
st.serialize_field("sum", &self.cyclomatic())?;
st.serialize_field("sum", &self.cyclomatic_sum())?;
st.serialize_field("average", &self.cyclomatic_average())?;
st.serialize_field("min", &self.cyclomatic_min())?;
st.serialize_field("max", &self.cyclomatic_max())?;
st.end()
}
}
Expand All @@ -37,31 +45,55 @@ impl fmt::Display for Stats {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
write!(
f,
"sum: {}, average: {}",
self.cyclomatic(),
self.cyclomatic_average()
"sum: {}, average: {}, min: {}, max: {}",
self.cyclomatic_sum(),
self.cyclomatic_average(),
self.cyclomatic_min(),
self.cyclomatic_max()
)
}
}

impl Stats {
/// Merges a second `Cyclomatic` metric into the first one
pub fn merge(&mut self, other: &Stats) {
self.cyclomatic += other.cyclomatic;
//Calculate minimum and maximum values
self.cyclomatic_max = self.cyclomatic_max.max(other.cyclomatic_max);
self.cyclomatic_min = self.cyclomatic_min.min(other.cyclomatic_min);

self.cyclomatic_sum += other.cyclomatic_sum;
self.n += other.n;
}

/// Returns the `Cyclomatic` metric value
pub fn cyclomatic(&self) -> f64 {
self.cyclomatic
}
/// Returns the sum
pub fn cyclomatic_sum(&self) -> f64 {
self.cyclomatic_sum
}

/// Returns the `Cyclomatic` metric average value
///
/// This value is computed dividing the `Cyclomatic` value for the
/// number of spaces.
pub fn cyclomatic_average(&self) -> f64 {
self.cyclomatic() / self.n as f64
self.cyclomatic_sum() / self.n as f64
}
/// Returns the `Cyclomatic` maximum value
pub fn cyclomatic_max(&self) -> f64 {
self.cyclomatic_max
}
/// Returns the `Cyclomatic` minimum value
pub fn cyclomatic_min(&self) -> f64 {
self.cyclomatic_min
}
/// Last step for computing minimum and maximum value and update cyclomatic_sum
pub fn compute_minmax(&mut self) {
self.cyclomatic_max = self.cyclomatic_max.max(self.cyclomatic);
self.cyclomatic_min = self.cyclomatic_min.min(self.cyclomatic);
self.cyclomatic_sum += self.cyclomatic;
}
}

Expand Down Expand Up @@ -190,9 +222,11 @@ mod tests {
"foo.py",
PythonParser,
cyclomatic,
[(cyclomatic, 6, usize)],
[(cyclomatic_sum, 6, usize)],
[
(cyclomatic_average, 3.0) // nspace = 2 (func and unit)
(cyclomatic_average, 3.0), // nspace = 2 (func and unit)
(cyclomatic_max, 5.0),
(cyclomatic_min, 1.0)
]
);
}
Expand All @@ -207,9 +241,11 @@ mod tests {
"foo.py",
PythonParser,
cyclomatic,
[(cyclomatic, 4, usize)],
[(cyclomatic_sum, 4, usize)],
[
(cyclomatic_average, 2.0) // nspace = 2 (func and unit)
(cyclomatic_average, 2.0), // nspace = 2 (func and unit)
(cyclomatic_max, 3.0),
(cyclomatic_min, 1.0)
]
);
}
Expand All @@ -228,9 +264,11 @@ mod tests {
"foo.rs",
RustParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[(cyclomatic_sum, 5, usize)],
[
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 1.0)
]
);
}
Expand All @@ -257,9 +295,11 @@ mod tests {
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[(cyclomatic_sum, 5, usize)],
[
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 1.0)
]
);
}
Expand All @@ -282,9 +322,87 @@ mod tests {
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic, 5, usize)],
[(cyclomatic_sum, 5, usize)],
[
(cyclomatic_average, 2.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 1.0)
]
);
}
#[test]
fn c_unit_before() {
check_metrics!(
"
int a=42;
if(a==42) //+2(+1 unit space)
{
}
if(a==34) //+1
{
}
int sumOfPrimes(int max) { // +1
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}",
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic_sum, 7, usize)],
[
(cyclomatic_average, 3.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 3.0)
]
);
}
/// Test to handle the case of min and max when merge happen before the final value of one module are setted.
/// In this case the min value should be 3 because the unit space has 2 branches and a complexity of 3
/// while the function sumOfPrimes has a complexity of 4.
#[test]
fn c_unit_after() {
check_metrics!(
"
int sumOfPrimes(int max) { // +1
int total = 0;
OUT: for (int i = 1; i <= max; ++i) { // +1
for (int j = 2; j < i; ++j) { // +1
if (i % j == 0) { // +1
continue OUT;
}
}
total += i;
}
return total;
}
int a=42;
if(a==42) //+2(+1 unit space)
{
}
if(a==34) //+1
{
}",
"foo.c",
CppParser,
cyclomatic,
[(cyclomatic_sum, 7, usize)],
[
(cyclomatic_average, 2.5) // nspace = 2 (func and unit)
(cyclomatic_average, 3.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 3.0)
]
);
}
Expand Down
2 changes: 1 addition & 1 deletion src/metrics/mi.rs
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ where
stats.halstead_length = halstead.length();
stats.halstead_vocabulary = halstead.vocabulary();
stats.halstead_volume = halstead.volume();
stats.cyclomatic = cyclomatic.cyclomatic();
stats.cyclomatic = cyclomatic.cyclomatic_sum();
stats.sloc = loc.sloc();
stats.comments_percentage = loc.cloc() / stats.sloc;
}
Expand Down
6 changes: 6 additions & 0 deletions src/spaces.rs
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,10 @@ fn compute_averages(state: &mut State) {
.nargs
.finalize(nom_functions, nom_closures);
}
#[inline(always)]
fn compute_minmax(state: &mut State) {
state.space.metrics.cyclomatic.compute_minmax();
}

fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
if state_stack.is_empty() {
Expand All @@ -203,11 +207,13 @@ fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
for _ in 0..diff_level {
if state_stack.len() == 1 {
let mut last_state = state_stack.last_mut().unwrap();
compute_minmax(&mut last_state);
compute_halstead_and_mi::<T>(&mut last_state);
compute_averages(&mut last_state);
break;
} else {
let mut state = state_stack.pop().unwrap();
compute_minmax(&mut state);
compute_halstead_and_mi::<T>(&mut state);
compute_averages(&mut state);

Expand Down

0 comments on commit a6fb78b

Please sign in to comment.