rust_code_analysis/metrics/
mi.rs

1use serde::ser::{SerializeStruct, Serializer};
2use serde::Serialize;
3use std::fmt;
4
5use super::cyclomatic;
6use super::halstead;
7use super::loc;
8
9use crate::checker::Checker;
10
11use crate::*;
12
13/// The `Mi` metric.
14#[derive(Default, Clone, Debug)]
15pub struct Stats {
16    halstead_length: f64,
17    halstead_vocabulary: f64,
18    halstead_volume: f64,
19    cyclomatic: f64,
20    sloc: f64,
21    comments_percentage: f64,
22}
23
24impl Serialize for Stats {
25    fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
26    where
27        S: Serializer,
28    {
29        let mut st = serializer.serialize_struct("maintanability_index", 3)?;
30        st.serialize_field("mi_original", &self.mi_original())?;
31        st.serialize_field("mi_sei", &self.mi_sei())?;
32        st.serialize_field("mi_visual_studio", &self.mi_visual_studio())?;
33        st.end()
34    }
35}
36
37impl fmt::Display for Stats {
38    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
39        write!(
40            f,
41            "mi_original: {}, mi_sei: {}, mi_visual_studio: {}",
42            self.mi_original(),
43            self.mi_sei(),
44            self.mi_visual_studio()
45        )
46    }
47}
48
49impl Stats {
50    pub(crate) fn merge(&mut self, _other: &Stats) {}
51
52    /// Returns the `Mi` metric calculated using the original formula.
53    ///
54    /// Its value can be negative.
55    #[inline(always)]
56    pub fn mi_original(&self) -> f64 {
57        // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm
58        171.0 - 5.2 * (self.halstead_volume).ln() - 0.23 * self.cyclomatic - 16.2 * self.sloc.ln()
59    }
60
61    /// Returns the `Mi` metric calculated using the derivative formula
62    /// employed by the Software Engineering Insitute (SEI).
63    ///
64    /// Its value can be negative.
65    #[inline(always)]
66    pub fn mi_sei(&self) -> f64 {
67        // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm
68        171.0 - 5.2 * self.halstead_volume.log2() - 0.23 * self.cyclomatic - 16.2 * self.sloc.log2()
69            + 50.0 * (self.comments_percentage * 2.4).sqrt().sin()
70    }
71
72    /// Returns the `Mi` metric calculated using the derivative formula
73    /// employed by Microsoft Visual Studio.
74    #[inline(always)]
75    pub fn mi_visual_studio(&self) -> f64 {
76        // http://www.projectcodemeter.com/cost_estimation/help/GL_maintainability.htm
77        let formula = 171.0
78            - 5.2 * self.halstead_volume.ln()
79            - 0.23 * self.cyclomatic
80            - 16.2 * self.sloc.ln();
81        (formula * 100.0 / 171.0).max(0.)
82    }
83}
84
85#[doc(hidden)]
86pub trait Mi
87where
88    Self: Checker,
89{
90    fn compute(
91        loc: &loc::Stats,
92        cyclomatic: &cyclomatic::Stats,
93        halstead: &halstead::Stats,
94        stats: &mut Stats,
95    ) {
96        stats.halstead_length = halstead.length();
97        stats.halstead_vocabulary = halstead.vocabulary();
98        stats.halstead_volume = halstead.volume();
99        stats.cyclomatic = cyclomatic.cyclomatic_sum();
100        stats.sloc = loc.sloc();
101        stats.comments_percentage = loc.cloc() / stats.sloc;
102    }
103}
104
105impl Mi for RustCode {}
106impl Mi for CppCode {}
107impl Mi for PythonCode {}
108impl Mi for MozjsCode {}
109impl Mi for JavascriptCode {}
110impl Mi for TypescriptCode {}
111impl Mi for TsxCode {}
112impl Mi for PreprocCode {}
113impl Mi for CcommentCode {}
114impl Mi for JavaCode {}
115
116#[cfg(test)]
117mod tests {
118    use std::path::PathBuf;
119
120    use super::*;
121
122    #[test]
123    fn check_mi_metrics() {
124        // This test checks that MI metric is computed correctly, so it verifies
125        // the calculations are correct, the adopted source code is irrelevant
126        check_metrics!(
127            "def f():
128                 pass",
129            "foo.py",
130            PythonParser,
131            mi,
132            [],
133            [
134                (mi_original, 151.203_315_883_223_2),
135                (mi_sei, 142.643_061_717_489_76),
136                (mi_visual_studio, 88.422_991_744_574_97),
137            ]
138        );
139    }
140}