use fxhash::FxHashMap;
use serde::Serialize;
use std::fmt;
use std::path::{Path, PathBuf};
use crate::checker::Checker;
use crate::node::Node;
use crate::abc::{self, Abc};
use crate::cognitive::{self, Cognitive};
use crate::cyclomatic::{self, Cyclomatic};
use crate::exit::{self, Exit};
use crate::getter::Getter;
use crate::halstead::{self, Halstead, HalsteadMaps};
use crate::loc::{self, Loc};
use crate::mi::{self, Mi};
use crate::nargs::{self, NArgs};
use crate::nom::{self, Nom};
use crate::npa::{self, Npa};
use crate::npm::{self, Npm};
use crate::wmc::{self, Wmc};
use crate::dump_metrics::*;
use crate::traits::*;
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize)]
#[serde(rename_all = "lowercase")]
pub enum SpaceKind {
#[default]
Unknown,
Function,
Class,
Struct,
Trait,
Impl,
Unit,
Namespace,
Interface,
}
impl fmt::Display for SpaceKind {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let s = match self {
SpaceKind::Unknown => "unknown",
SpaceKind::Function => "function",
SpaceKind::Class => "class",
SpaceKind::Struct => "struct",
SpaceKind::Trait => "trait",
SpaceKind::Impl => "impl",
SpaceKind::Unit => "unit",
SpaceKind::Namespace => "namespace",
SpaceKind::Interface => "interface",
};
write!(f, "{}", s)
}
}
#[derive(Default, Debug, Clone, Serialize)]
pub struct CodeMetrics {
pub nargs: nargs::Stats,
pub nexits: exit::Stats,
pub cognitive: cognitive::Stats,
pub cyclomatic: cyclomatic::Stats,
pub halstead: halstead::Stats,
pub loc: loc::Stats,
pub nom: nom::Stats,
pub mi: mi::Stats,
pub abc: abc::Stats,
#[serde(skip_serializing_if = "wmc::Stats::is_disabled")]
pub wmc: wmc::Stats,
#[serde(skip_serializing_if = "npm::Stats::is_disabled")]
pub npm: npm::Stats,
#[serde(skip_serializing_if = "npa::Stats::is_disabled")]
pub npa: npa::Stats,
}
impl fmt::Display for CodeMetrics {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
writeln!(f, "{}", self.nargs)?;
writeln!(f, "{}", self.nexits)?;
writeln!(f, "{}", self.cognitive)?;
writeln!(f, "{}", self.cyclomatic)?;
writeln!(f, "{}", self.halstead)?;
writeln!(f, "{}", self.loc)?;
writeln!(f, "{}", self.nom)?;
write!(f, "{}", self.mi)
}
}
impl CodeMetrics {
pub fn merge(&mut self, other: &CodeMetrics) {
self.cognitive.merge(&other.cognitive);
self.cyclomatic.merge(&other.cyclomatic);
self.halstead.merge(&other.halstead);
self.loc.merge(&other.loc);
self.nom.merge(&other.nom);
self.mi.merge(&other.mi);
self.nargs.merge(&other.nargs);
self.nexits.merge(&other.nexits);
self.abc.merge(&other.abc);
self.wmc.merge(&other.wmc);
self.npm.merge(&other.npm);
self.npa.merge(&other.npa);
}
}
#[derive(Debug, Clone, Serialize)]
pub struct FuncSpace {
pub name: Option<String>,
pub start_line: usize,
pub end_line: usize,
pub kind: SpaceKind,
pub spaces: Vec<FuncSpace>,
pub metrics: CodeMetrics,
}
impl FuncSpace {
fn new<T: Getter>(node: &Node, code: &[u8], kind: SpaceKind) -> Self {
let (start_position, end_position) = match kind {
SpaceKind::Unit => {
if node.object().child_count() == 0 {
(0, 0)
} else {
(
node.object().start_position().row + 1,
node.object().end_position().row,
)
}
}
_ => (
node.object().start_position().row + 1,
node.object().end_position().row + 1,
),
};
Self {
name: T::get_func_space_name(node, code)
.map(|name| name.split_whitespace().collect::<Vec<_>>().join(" ")),
spaces: Vec::new(),
metrics: CodeMetrics::default(),
kind,
start_line: start_position,
end_line: end_position,
}
}
}
#[inline(always)]
fn compute_halstead_mi_and_wmc<T: ParserTrait>(state: &mut State) {
state
.halstead_maps
.finalize(&mut state.space.metrics.halstead);
T::Mi::compute(
&state.space.metrics.loc,
&state.space.metrics.cyclomatic,
&state.space.metrics.halstead,
&mut state.space.metrics.mi,
);
T::Wmc::compute(
state.space.kind,
&state.space.metrics.cyclomatic,
&mut state.space.metrics.wmc,
);
}
#[inline(always)]
fn compute_averages(state: &mut State) {
let nom_functions = state.space.metrics.nom.functions_sum() as usize;
let nom_closures = state.space.metrics.nom.closures_sum() as usize;
let nom_total = state.space.metrics.nom.total() as usize;
state.space.metrics.cognitive.finalize(nom_total);
state.space.metrics.nexits.finalize(nom_total);
state
.space
.metrics
.nargs
.finalize(nom_functions, nom_closures);
}
#[inline(always)]
fn compute_minmax(state: &mut State) {
state.space.metrics.cyclomatic.compute_minmax();
state.space.metrics.nexits.compute_minmax();
state.space.metrics.cognitive.compute_minmax();
state.space.metrics.nargs.compute_minmax();
state.space.metrics.nom.compute_minmax();
state.space.metrics.loc.compute_minmax();
state.space.metrics.abc.compute_minmax();
}
#[inline(always)]
fn compute_sum(state: &mut State) {
state.space.metrics.wmc.compute_sum();
state.space.metrics.npm.compute_sum();
state.space.metrics.npa.compute_sum();
}
fn finalize<T: ParserTrait>(state_stack: &mut Vec<State>, diff_level: usize) {
if state_stack.is_empty() {
return;
}
for _ in 0..diff_level {
if state_stack.len() == 1 {
let last_state = state_stack.last_mut().unwrap();
compute_minmax(last_state);
compute_sum(last_state);
compute_halstead_mi_and_wmc::<T>(last_state);
compute_averages(last_state);
break;
} else {
let mut state = state_stack.pop().unwrap();
compute_minmax(&mut state);
compute_sum(&mut state);
compute_halstead_mi_and_wmc::<T>(&mut state);
compute_averages(&mut state);
let last_state = state_stack.last_mut().unwrap();
last_state.halstead_maps.merge(&state.halstead_maps);
compute_halstead_mi_and_wmc::<T>(last_state);
last_state.space.metrics.merge(&state.space.metrics);
last_state.space.spaces.push(state.space);
}
}
}
#[derive(Debug, Clone)]
struct State<'a> {
space: FuncSpace,
halstead_maps: HalsteadMaps<'a>,
}
pub fn metrics<'a, T: ParserTrait>(parser: &'a T, path: &'a Path) -> Option<FuncSpace> {
let code = parser.get_code();
let node = parser.get_root();
let mut cursor = node.object().walk();
let mut stack = Vec::new();
let mut children = Vec::new();
let mut state_stack: Vec<State> = Vec::new();
let mut last_level = 0;
let mut nesting_map = FxHashMap::<usize, (usize, usize, usize)>::default();
nesting_map.insert(node.object().id(), (0, 0, 0));
stack.push((node, 0));
while let Some((node, level)) = stack.pop() {
if level < last_level {
finalize::<T>(&mut state_stack, last_level - level);
last_level = level;
}
let kind = T::Getter::get_space_kind(&node);
let func_space = T::Checker::is_func(&node) || T::Checker::is_func_space(&node);
let unit = kind == SpaceKind::Unit;
let new_level = if func_space {
let state = State {
space: FuncSpace::new::<T::Getter>(&node, code, kind),
halstead_maps: HalsteadMaps::new(),
};
state_stack.push(state);
last_level = level + 1;
last_level
} else {
level
};
if let Some(state) = state_stack.last_mut() {
let last = &mut state.space;
T::Cognitive::compute(&node, &mut last.metrics.cognitive, &mut nesting_map);
T::Cyclomatic::compute(&node, &mut last.metrics.cyclomatic);
T::Halstead::compute(&node, code, &mut state.halstead_maps);
T::Loc::compute(&node, &mut last.metrics.loc, func_space, unit);
T::Nom::compute(&node, &mut last.metrics.nom);
T::NArgs::compute(&node, &mut last.metrics.nargs);
T::Exit::compute(&node, &mut last.metrics.nexits);
T::Abc::compute(&node, &mut last.metrics.abc);
T::Npm::compute(&node, &mut last.metrics.npm);
T::Npa::compute(&node, &mut last.metrics.npa);
}
cursor.reset(node.object());
if cursor.goto_first_child() {
loop {
children.push((Node::new(cursor.node()), new_level));
if !cursor.goto_next_sibling() {
break;
}
}
for child in children.drain(..).rev() {
stack.push(child);
}
}
}
finalize::<T>(&mut state_stack, std::usize::MAX);
state_stack.pop().map(|mut state| {
state.space.name = path.to_str().map(|name| name.to_string());
state.space
})
}
pub struct MetricsCfg {
pub path: PathBuf,
}
pub struct Metrics {
_guard: (),
}
impl Callback for Metrics {
type Res = std::io::Result<()>;
type Cfg = MetricsCfg;
fn call<T: ParserTrait>(cfg: Self::Cfg, parser: &T) -> Self::Res {
if let Some(space) = metrics(parser, &cfg.path) {
dump_root(&space)
} else {
Ok(())
}
}
}