pub mod language;
pub use language::Tier1Language;
use crate::Result;
use codelore_rca::{
FuncSpace, JavaParser, JavascriptParser, ParserTrait, PythonParser, RustParser, SpaceKind,
TsxParser, TypescriptParser, metrics,
};
use std::path::Path;
#[derive(Debug, Clone)]
pub struct ComplexityEntity {
pub path: String,
pub name: String,
pub kind: String, pub start_line: u32,
pub end_line: u32,
pub cyclomatic: f64,
pub cognitive: f64,
pub halstead_volume: Option<f64>,
pub halstead_difficulty: Option<f64>,
pub halstead_effort: Option<f64>,
pub mi: Option<f64>,
pub nom: u32,
pub nexits: u32,
pub nargs: u32,
pub loc: u32,
pub sloc: u32,
pub max_nesting: u32,
pub mean_nesting: f64,
pub sd_nesting: f64,
pub total_nesting: u32,
pub bool_ops: u32,
}
fn space_kind_str(kind: SpaceKind) -> &'static str {
match kind {
SpaceKind::Function => "function",
SpaceKind::Class => "class",
SpaceKind::Struct => "struct",
SpaceKind::Trait => "trait",
SpaceKind::Impl => "impl",
SpaceKind::Unit => "unit",
SpaceKind::Namespace => "namespace",
SpaceKind::Interface => "interface",
SpaceKind::Unknown => "other",
}
}
fn collect_entities(space: &FuncSpace, path: &str, out: &mut Vec<ComplexityEntity>) {
let m = &space.metrics;
let h_volume = {
let v = m.halstead.volume();
if v.is_finite() { Some(v) } else { None }
};
let h_difficulty = {
let d = m.halstead.difficulty();
if d.is_finite() { Some(d) } else { None }
};
let h_effort = {
let e = m.halstead.effort();
if e.is_finite() { Some(e) } else { None }
};
let mi_val = {
let v = m.mi.mi_sei();
if v.is_finite() { Some(v) } else { None }
};
let f_to_u32 = |v: f64| -> u32 {
if v.is_finite() && v >= 0.0 {
let clamped = v.round().min(f64::from(u32::MAX));
#[allow(clippy::cast_possible_truncation, clippy::cast_sign_loss)]
{
clamped as u32
}
} else {
0
}
};
let entity = ComplexityEntity {
path: path.to_owned(),
name: space
.name
.clone()
.unwrap_or_else(|| "<anonymous>".to_owned()),
kind: space_kind_str(space.kind).to_owned(),
start_line: u32::try_from(space.start_line).unwrap_or(u32::MAX),
end_line: u32::try_from(space.end_line).unwrap_or(u32::MAX),
cyclomatic: m.cyclomatic.cyclomatic(),
cognitive: m.cognitive.cognitive(),
halstead_volume: h_volume,
halstead_difficulty: h_difficulty,
halstead_effort: h_effort,
mi: mi_val,
nom: f_to_u32(m.nom.total()),
nexits: f_to_u32(m.nexits.exit_sum()),
nargs: f_to_u32(m.nargs.fn_args() + m.nargs.closure_args()),
loc: f_to_u32(m.loc.ploc()),
sloc: f_to_u32(m.loc.sloc()),
max_nesting: f_to_u32(m.cognitive.max_nesting()),
mean_nesting: 0.0,
sd_nesting: 0.0,
total_nesting: f_to_u32(m.cognitive.total_nesting()),
bool_ops: f_to_u32(m.cognitive.bool_ops()),
};
out.push(entity);
for child in &space.spaces {
collect_entities(child, path, out);
}
}
fn metrics_with_guard<T: ParserTrait>(source: Vec<u8>, path: &Path) -> Option<FuncSpace> {
let parser = T::new(source, path, None);
if parser.get_root().has_error() {
tracing::debug!(
"complexity: parse errors in {} — metrics computed on a partial tree",
path.display()
);
}
metrics(&parser, path)
}
pub fn compute_for_file(
path: &Path,
source: Vec<u8>,
lang: Tier1Language,
) -> Result<Vec<ComplexityEntity>> {
let path_str = path.to_str().unwrap_or("");
let root: Option<FuncSpace> = match lang {
Tier1Language::Rust => metrics_with_guard::<RustParser>(source, path),
Tier1Language::Python => metrics_with_guard::<PythonParser>(source, path),
Tier1Language::Java => metrics_with_guard::<JavaParser>(source, path),
Tier1Language::JavaScript => metrics_with_guard::<JavascriptParser>(source, path),
Tier1Language::TypeScript => metrics_with_guard::<TypescriptParser>(source, path),
Tier1Language::Tsx => metrics_with_guard::<TsxParser>(source, path),
};
let mut entities = Vec::new();
if let Some(space) = root {
collect_entities(&space, path_str, &mut entities);
}
Ok(entities)
}