Skip to main content

rust_code_analysis_code_split/metrics/
mi.rs

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