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
This commit is contained in:
giovannitangredi 2021-11-25 17:49:35 +01:00 коммит произвёл GitHub
Родитель a507c7dba1
Коммит a6fb78bc9e
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
4 изменённых файлов: 146 добавлений и 22 удалений

Просмотреть файл

@ -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},
@ -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},
@ -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},
@ -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},
@ -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},

Просмотреть файл

@ -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,
}
}
}
@ -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()
}
}
@ -37,9 +45,11 @@ 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()
)
}
}
@ -47,7 +57,11 @@ impl fmt::Display for Stats {
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;
}
@ -55,13 +69,31 @@ impl Stats {
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;
}
}
@ -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)
]
);
}
@ -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)
]
);
}
@ -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)
]
);
}
@ -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)
]
);
}
@ -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_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, 3.5), // nspace = 2 (func and unit)
(cyclomatic_max, 4.0),
(cyclomatic_min, 3.0)
]
);
}

Просмотреть файл

@ -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;
}

Просмотреть файл

@ -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() {
@ -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);