1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
use std::{collections::HashMap, fmt::Display, path::Path};
use rust_code_analysis::{metrics, read_file_with_eol, CodeMetrics, ParserTrait, RustParser};
pub trait MetricReader {
fn get_cyclomatic_from_path_and_content(&self, path: &Path) -> Option<f64>;
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub struct Churn(i32);
impl Churn {
pub fn as_f64(self) -> f64 {
self.0 as f64
}
}
impl From<i32> for Churn {
fn from(src: i32) -> Self {
Self(src)
}
}
impl Display for Churn {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
self.0.fmt(f)
}
}
pub struct CodeAnalysisReader {}
impl Default for CodeAnalysisReader {
fn default() -> Self {
Self::new()
}
}
impl CodeAnalysisReader {
pub fn new() -> Self {
Self {}
}
fn metric_from_path_and_content(
&self,
content: Option<Vec<u8>>,
path: &Path,
) -> Option<CodeMetrics> {
let parser = RustParser::new(content.expect("file content"), path, None);
metrics(&parser, path).map(|v| v.metrics)
}
}
impl MetricReader for CodeAnalysisReader {
fn get_cyclomatic_from_path_and_content(&self, path: &Path) -> Option<f64> {
read_file_with_eol(path)
.ok()
.and_then(|file| self.metric_from_path_and_content(file, path))
.map(|metrics| metrics.cyclomatic.cyclomatic_sum())
}
}
#[derive(Debug, Clone, PartialEq)]
pub struct FileMetrics {
pub filename: String,
pub churn: Churn,
pub complexity: f64,
pub magnitude: f64,
}
impl FileMetrics {
pub fn new(filename: String, churn: Churn, complexity: f64) -> Self {
let origin = (0.0, 0.0);
let distance_to_origin =
((origin.0 - churn.as_f64()).powi(2) + (origin.1 - complexity as f64).powi(2)).sqrt();
Self {
filename,
churn,
complexity,
magnitude: distance_to_origin,
}
}
pub fn to_point(&self) -> (f64, f64) {
(self.churn.as_f64(), self.complexity)
}
}
pub fn metrics_per_file(
file_map: HashMap<String, Churn>,
reader: impl MetricReader,
) -> Vec<FileMetrics> {
file_map
.into_iter() .filter_map(|(filename, churn)| {
let path = Path::new(&filename);
let complexity = reader.get_cyclomatic_from_path_and_content(path);
complexity.map(|complexity| FileMetrics::new(filename.to_string(), churn, complexity))
})
.collect()
}
#[cfg(test)]
mod tests {
use std::{collections::HashMap, path::Path};
use crate::metrics::Churn;
use super::{metrics_per_file, MetricReader};
struct TestReader {}
impl MetricReader for TestReader {
fn get_cyclomatic_from_path_and_content(&self, _path: &Path) -> Option<f64> {
Some(1.0)
}
}
#[test]
fn build_array_of_metric() {
let file_map = HashMap::from([("file".to_string(), Churn::from(1))]);
let results = metrics_per_file(file_map, TestReader {});
assert!(results.len() == 1);
assert!(results[0].churn == Churn::from(1));
assert!(results[0].complexity == 1.0);
}
}