Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down
3 changes: 3 additions & 0 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand Down
42 changes: 41 additions & 1 deletion src/metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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::*;

Expand All @@ -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,
}
Expand All @@ -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()
Expand All @@ -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(),
}
Expand All @@ -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)
}
Expand All @@ -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);
}
Expand Down Expand Up @@ -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(
Expand Down Expand Up @@ -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,
Expand Down Expand Up @@ -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);
}
Expand Down
113 changes: 113 additions & 0 deletions src/mi.rs
Original file line number Diff line number Diff line change
@@ -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<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
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 {}
2 changes: 2 additions & 0 deletions src/traits.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;

Expand Down
5 changes: 4 additions & 1 deletion src/ts_parser.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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<u8>,
tree: Tree,
Expand Down Expand Up @@ -79,6 +80,7 @@ impl<
+ Exit
+ Halstead
+ Loc
+ Mi
+ NArgs,
> TSParserTrait for TSParser<T>
{
Expand All @@ -87,6 +89,7 @@ impl<
type Cyclomatic = T;
type Halstead = T;
type Loc = T;
type Mi = T;
type NArgs = T;
type Exit = T;

Expand Down
25 changes: 20 additions & 5 deletions src/web/server.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand All @@ -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": []}]}
});
Expand Down Expand Up @@ -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": []}
});
Expand Down Expand Up @@ -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,
Expand All @@ -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": []}]}
});
Expand Down