From 33fdf786e1adb8301a17739b4905e4b4327cf22a Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Mon, 23 Mar 2020 16:38:28 +0100 Subject: [PATCH 1/3] Implement Mi metric --- src/lib.rs | 3 ++ src/metrics.rs | 42 +++++++++++++++++- src/mi.rs | 113 +++++++++++++++++++++++++++++++++++++++++++++++ src/traits.rs | 2 + src/ts_parser.rs | 5 ++- 5 files changed, 163 insertions(+), 2 deletions(-) create mode 100644 src/mi.rs diff --git a/src/lib.rs b/src/lib.rs index 07ed54bb0..9a8220402 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -34,6 +34,9 @@ pub use crate::exit::*; pub mod loc; pub use crate::loc::*; +pub mod mi; +pub use crate::mi::*; + pub mod halstead; pub use crate::halstead::*; diff --git a/src/metrics.rs b/src/metrics.rs index 340d0c97f..b4c67942c 100644 --- a/src/metrics.rs +++ b/src/metrics.rs @@ -15,6 +15,7 @@ use crate::fn_args::{self, NArgs}; use crate::getter::Getter; use crate::halstead::{self, Halstead}; use crate::loc::{self, Loc}; +use crate::mi::{self, Mi}; use crate::tools::write_file; use crate::traits::*; @@ -23,6 +24,7 @@ pub struct CodeMetrics<'a> { pub cyclomatic: cyclomatic::Stats, pub halstead: halstead::Stats<'a>, pub loc: loc::Stats, + pub mi: mi::Stats, pub nargs: fn_args::Stats, pub nexits: exit::Stats, } @@ -36,6 +38,7 @@ impl<'a> Serialize for CodeMetrics<'a> { st.serialize_field("cyclomatic", &self.cyclomatic)?; st.serialize_field("halstead", &self.halstead)?; st.serialize_field("loc", &self.loc)?; + st.serialize_field("mi", &self.mi)?; st.serialize_field("nargs", &self.nargs)?; st.serialize_field("nexits", &self.nexits)?; st.end() @@ -48,6 +51,7 @@ impl<'a> Default for CodeMetrics<'a> { cyclomatic: cyclomatic::Stats::default(), halstead: halstead::Stats::default(), loc: loc::Stats::default(), + mi: mi::Stats::default(), nargs: fn_args::Stats::default(), nexits: exit::Stats::default(), } @@ -59,6 +63,7 @@ impl<'a> fmt::Display for CodeMetrics<'a> { writeln!(f, "{}", self.cyclomatic)?; writeln!(f, "{}", self.halstead)?; writeln!(f, "{}", self.loc)?; + writeln!(f, "{}", self.mi)?; writeln!(f, "{}", self.nargs)?; write!(f, "{}", self.nexits) } @@ -69,6 +74,7 @@ impl<'a> CodeMetrics<'a> { self.cyclomatic.merge(&other.cyclomatic); self.halstead.merge(&other.halstead); self.loc.merge(&other.loc); + self.mi.merge(&other.mi); self.nargs.merge(&other.nargs); self.nexits.merge(&other.nexits); } @@ -183,7 +189,8 @@ impl<'a> FuncSpace<'a> { Self::dump_nargs(&metrics.nargs, &prefix, false, stdout)?; Self::dump_nexits(&metrics.nexits, &prefix, false, stdout)?; Self::dump_halstead(&metrics.halstead, &prefix, false, stdout)?; - Self::dump_loc(&metrics.loc, &prefix, true, stdout) + Self::dump_loc(&metrics.loc, &prefix, false, stdout)?; + Self::dump_mi(&metrics.mi, &prefix, true, stdout) } fn dump_cyclomatic( @@ -263,6 +270,32 @@ impl<'a> FuncSpace<'a> { Self::dump_value("cloc", stats.cloc(), &prefix, true, stdout) } + fn dump_mi( + stats: &mi::Stats, + prefix: &str, + last: bool, + stdout: &mut StandardStreamLock, + ) -> std::io::Result<()> { + let (pref_child, pref) = if last { (" ", "`- ") } else { ("| ", "|- ") }; + + color!(stdout, Blue); + write!(stdout, "{}{}", prefix, pref)?; + + color!(stdout, Green, true); + writeln!(stdout, "mi")?; + + let prefix = format!("{}{}", prefix, pref_child); + Self::dump_value("mi_original", stats.mi_original(), &prefix, false, stdout)?; + Self::dump_value("mi_sei", stats.mi_sei(), &prefix, false, stdout)?; + Self::dump_value( + "mi_visual_studio", + stats.mi_visual_studio(), + &prefix, + true, + stdout, + ) + } + fn dump_nargs( stats: &fn_args::Stats, prefix: &str, @@ -385,6 +418,13 @@ pub fn metrics<'a, T: TSParserTrait>(parser: &'a T, path: &'a PathBuf) -> Option T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic); T::Halstead::compute(&node, code, &mut last.metrics.halstead); T::Loc::compute(&node, code, &mut last.metrics.loc, func_space, unit); + T::Mi::compute( + &node, + &last.metrics.loc, + &last.metrics.cyclomatic, + &last.metrics.halstead, + &mut last.metrics.mi, + ); T::NArgs::compute(&node, &mut last.metrics.nargs); T::Exit::compute(&node, &mut last.metrics.nexits); } diff --git a/src/mi.rs b/src/mi.rs new file mode 100644 index 000000000..d76884384 --- /dev/null +++ b/src/mi.rs @@ -0,0 +1,113 @@ +use serde::ser::{SerializeStruct, Serializer}; +use serde::Serialize; +use std::fmt; +use tree_sitter::Node; + +use crate::cyclomatic; +use crate::halstead; +use crate::loc; + +use crate::checker::Checker; + +use crate::*; + +#[derive(Default, Debug)] +pub struct Stats { + halstead_length: f64, + halstead_vocabulary: f64, + halstead_volume: f64, + cyclomatic: f64, + sloc: f64, + comments_percentage: f64, +} + +impl Serialize for Stats { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + let mut st = serializer.serialize_struct("maintanability_index", 3)?; + st.serialize_field("mi_original", &self.mi_original())?; + st.serialize_field("mi_sei", &self.mi_sei())?; + st.serialize_field("mi_visual_studio", &self.mi_visual_studio())?; + st.end() + } +} + +impl fmt::Display for Stats { + fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { + write!( + f, + "mi_original: {}, mi_sei: {}, mi_visual_studio: {}", + self.mi_original(), + self.mi_sei(), + self.mi_visual_studio() + ) + } +} + +impl Stats { + pub fn merge(&mut self, other: &Stats) { + self.halstead_length += other.halstead_length; + self.halstead_vocabulary += other.halstead_vocabulary; + self.halstead_volume = self.halstead_length * self.halstead_vocabulary.log2(); + } + + #[inline(always)] + pub fn mi_original(&self) -> f64 { + // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm + 171.0 - 5.2 * (self.halstead_volume).ln() - 0.23 * self.cyclomatic - 16.2 * self.sloc.ln() + } + + #[inline(always)] + pub fn mi_sei(&self) -> f64 { + // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm + 171.0 - 5.2 * self.halstead_volume.log2() - 0.23 * self.cyclomatic - 16.2 * self.sloc.log2() + + 50.0 * (self.comments_percentage * 2.4).sqrt().sin() + } + + #[inline(always)] + pub fn mi_visual_studio(&self) -> f64 { + // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm + let formula = 171.0 + - 5.2 * self.halstead_volume.ln() + - 0.23 * self.cyclomatic + - 16.2 * self.sloc.ln(); + (formula * 100.0 / 171.0).max(0.) + } +} + +pub trait Mi +where + Self: Checker, +{ + fn compute<'a>( + _node: &Node<'a>, + loc: &loc::Stats, + cyclomatic: &cyclomatic::Stats, + halstead: &halstead::Stats<'a>, + stats: &mut Stats, + ) { + stats.halstead_length = halstead.length(); + stats.halstead_vocabulary = halstead.vocabulary(); + stats.halstead_volume = halstead.volume(); + stats.cyclomatic = cyclomatic.cyclomatic(); + stats.sloc = loc.sloc(); + stats.comments_percentage = loc.cloc() / stats.sloc; + } +} + +impl Mi for RustCode {} +impl Mi for CppCode {} +impl Mi for PythonCode {} +impl Mi for MozjsCode {} +impl Mi for JavascriptCode {} +impl Mi for TypescriptCode {} +impl Mi for TsxCode {} +impl Mi for PreprocCode {} +impl Mi for CcommentCode {} +impl Mi for CSharpCode {} +impl Mi for JavaCode {} +impl Mi for GoCode {} +impl Mi for CssCode {} +impl Mi for HtmlCode {} diff --git a/src/traits.rs b/src/traits.rs index d46f89359..15a5ef4b0 100644 --- a/src/traits.rs +++ b/src/traits.rs @@ -10,6 +10,7 @@ use crate::getter::Getter; use crate::halstead::Halstead; use crate::languages::*; use crate::loc::Loc; +use crate::mi::Mi; use crate::preproc::PreprocResults; use crate::ts_parser::Filter; use crate::web::alterator::Alterator; @@ -28,6 +29,7 @@ pub trait TSParserTrait { type Cyclomatic: Cyclomatic; type Halstead: Halstead; type Loc: Loc; + type Mi: Mi; type NArgs: NArgs; type Exit: Exit; diff --git a/src/ts_parser.rs b/src/ts_parser.rs index 6deb9a1a6..116328c3d 100644 --- a/src/ts_parser.rs +++ b/src/ts_parser.rs @@ -12,12 +12,13 @@ use crate::getter::Getter; use crate::halstead::Halstead; use crate::languages::*; use crate::loc::Loc; +use crate::mi::Mi; use crate::preproc::{get_macros, PreprocResults}; use crate::traits::*; use crate::web::alterator::Alterator; pub struct TSParser< - T: TSLanguage + Checker + Getter + Alterator + Cyclomatic + Exit + Halstead + NArgs + Loc, + T: TSLanguage + Checker + Getter + Alterator + Cyclomatic + Exit + Halstead + NArgs + Loc + Mi, > { code: Vec, tree: Tree, @@ -79,6 +80,7 @@ impl< + Exit + Halstead + Loc + + Mi + NArgs, > TSParserTrait for TSParser { @@ -87,6 +89,7 @@ impl< type Cyclomatic = T; type Halstead = T; type Loc = T; + type Mi = T; type NArgs = T; type Exit = T; From adef0dbbc056eec6edb6c090a9bc58842d957516 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 31 Mar 2020 18:33:16 +0200 Subject: [PATCH 2/3] Update tests --- src/web/server.rs | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/src/web/server.rs b/src/web/server.rs index 99613c50c..112a00fc2 100644 --- a/src/web/server.rs +++ b/src/web/server.rs @@ -650,7 +650,10 @@ mod tests { "n2": 1.0, "n1": 2.0, "volume": 4.754_887_502_163_468}, - "loc": {"cloc": 1.0, "lloc": 2.0, "sloc": 4.0}}, + "loc": {"cloc": 1.0, "lloc": 2.0, "sloc": 4.0}, + "mi": {"mi_original": 140.204_331_558_152_12, + "mi_sei": 161.644_455_240_662_24, + "mi_visual_studio": 81.990_837_168_510_01}}, "name": "test.py", "spaces": [{"kind": "function", "start_line": 3, @@ -672,7 +675,10 @@ mod tests { "n2": 1.0, "n1": 2.0, "volume": 4.754_887_502_163_468}, - "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}}, + "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "mi": {"mi_original": 151.433_315_883_223_23, + "mi_sei": 142.873_061_717_489_78, + "mi_visual_studio": 88.557_494_668_551_6}}, "name": "foo", "spaces": []}]} }); @@ -720,7 +726,10 @@ mod tests { "n2": 1.0, "n1": 2.0, "volume": 4.754_887_502_163_468}, - "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}}, + "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "mi": {"mi_original": 151.433_315_883_223_23, + "mi_sei": 142.873_061_717_489_78, + "mi_visual_studio": 88.557_494_668_551_6}}, "name": "test.py", "spaces": []} }); @@ -764,7 +773,10 @@ mod tests { "n2": 1.0, "n1": 2.0, "volume": 4.754_887_502_163_468}, - "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}}, + "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "mi": {"mi_original": 151.433_315_883_223_23, + "mi_sei": 142.873_061_717_489_78, + "mi_visual_studio": 88.557_494_668_551_6}}, "name": "test.py", "spaces": [{"kind": "function", "start_line": 1, @@ -786,7 +798,10 @@ mod tests { "n2": 1.0, "n1": 2.0, "volume": 4.754_887_502_163_468}, - "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}}, + "loc": {"cloc": 0.0, "lloc": 2.0, "sloc": 2.0}, + "mi": {"mi_original": 151.433_315_883_223_23, + "mi_sei": 142.873_061_717_489_78, + "mi_visual_studio": 88.557_494_668_551_6}}, "name": "foo", "spaces": []}]} }); From 2d23490cb2225fb8f235d85218e5051d9d399b24 Mon Sep 17 00:00:00 2001 From: Luni-4 Date: Tue, 31 Mar 2020 18:33:39 +0200 Subject: [PATCH 3/3] Add Mi to README --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index f40a6a69a..f1f55a8d0 100644 --- a/README.md +++ b/README.md @@ -25,6 +25,7 @@ control flow of a program. - SLOC: it counts the number of lines in a source file. - LLOC: it counts the number of logical lines (instructions) contained in a source file. - HALSTEAD: it is a suite that provides a series of information, such as the effort required to maintain the analyzed code, the size in bits to store the program, the difficulty to understand the code, an estimate of the number of bugs present in the codebase, and an estimate of the time needed to implement the software. +- MI: it is a suite that allows to evaluate the maintainability of a software. - NEXITS: it counts the number of possible exit points from a method/function. - NARGS: it counts the number of arguments of a function/method.