mod cognitive;
mod common;
mod complexity;
mod definition;
mod dependency;
mod derived;
mod documentation;
mod duplication;
mod function;
mod halstead;
mod markdown;
mod nesting;
mod size;
use crate::ir::{File, Function, Program};
use std::collections::BTreeMap;
pub use definition::{Applicability, DEFINITION_VERSION, MetricDefinition, Scope};
pub use markdown::{reference_pages, to_markdown};
#[derive(Debug, Clone, PartialEq)]
pub enum MetricValue {
Available(f64),
NotApplicable,
Unsupported,
Error(String),
}
impl MetricValue {
pub fn ratio(numerator: f64, denominator: f64) -> MetricValue {
if denominator == 0.0 {
MetricValue::NotApplicable
} else {
MetricValue::Available(numerator / denominator)
}
}
}
impl MetricValue {
pub fn mean(values: &[usize]) -> MetricValue {
MetricValue::ratio(values.iter().sum::<usize>() as f64, values.len() as f64)
}
pub fn max(values: &[usize]) -> MetricValue {
values
.iter()
.max()
.map_or(MetricValue::NotApplicable, |&x| x.into())
}
}
impl From<usize> for MetricValue {
fn from(v: usize) -> Self {
MetricValue::Available(v as f64)
}
}
impl From<f64> for MetricValue {
fn from(v: f64) -> Self {
MetricValue::Available(v)
}
}
pub type Metrics = BTreeMap<&'static str, MetricValue>;
#[derive(Debug, Clone, PartialEq, Default)]
pub struct FileMetrics {
pub metrics: Metrics,
pub functions: Vec<Metrics>,
}
#[derive(Debug, Clone, PartialEq, Default)]
pub struct ProgramMetrics {
pub project: Metrics,
pub files: Vec<FileMetrics>,
}
impl ProgramMetrics {
fn merge(&mut self, other: ProgramMetrics) {
self.project.extend(other.project);
if self.files.is_empty() {
self.files = other.files;
return;
}
assert_eq!(
self.files.len(),
other.files.len(),
"calculators must report every file"
);
for (mine, theirs) in self.files.iter_mut().zip(other.files) {
assert_eq!(
mine.functions.len(),
theirs.functions.len(),
"calculators must report every function"
);
mine.metrics.extend(theirs.metrics);
for (f, g) in mine.functions.iter_mut().zip(theirs.functions) {
f.extend(g);
}
}
}
}
pub trait Calculator {
fn definitions(&self) -> &'static [MetricDefinition];
fn compute(&self, program: &Program) -> ProgramMetrics;
}
pub(crate) fn per_file<T>(
program: &Program,
prepare: impl Fn(&File) -> T,
file_metrics: impl Fn(&File, &T) -> Metrics,
function_metrics: impl Fn(&File, &T, &Function) -> Metrics,
) -> ProgramMetrics {
let files = program
.files
.iter()
.map(|file| {
let prepared = prepare(file);
FileMetrics {
metrics: file_metrics(file, &prepared),
functions: file
.functions
.iter()
.map(|f| function_metrics(file, &prepared, f))
.collect(),
}
})
.collect();
ProgramMetrics {
project: Metrics::new(),
files,
}
}
pub fn calculators() -> Vec<Box<dyn Calculator>> {
vec![
Box::new(size::SizeCalculator),
Box::new(complexity::ComplexityCalculator),
Box::new(cognitive::CognitiveCalculator),
Box::new(nesting::NestingCalculator),
Box::new(halstead::HalsteadCalculator),
Box::new(function::FunctionCalculator),
Box::new(duplication::DuplicationCalculator),
Box::new(dependency::DependencyCalculator),
Box::new(documentation::DocumentationCalculator),
]
}
pub fn definitions() -> Vec<&'static MetricDefinition> {
let standard: Vec<_> = calculators()
.iter()
.flat_map(|c| c.definitions().iter())
.collect();
standard.into_iter().chain(derived::definitions()).collect()
}
pub fn compute(program: &Program) -> ProgramMetrics {
let mut result = ProgramMetrics::default();
for calculator in calculators() {
result.merge(calculator.compute(program));
}
derived::derive(Scope::Project, &mut result.project);
for file in &mut result.files {
derived::derive(Scope::File, &mut file.metrics);
for function in &mut file.functions {
derived::derive(Scope::Function, function);
}
}
result
}