use super::contract::component_for;
use crate::engine::RepositoryState;
use std::collections::{BTreeMap, BTreeSet};
use std::path::Path;
use weavatrix_graph::NodeKind;
mod function_extent;
pub(super) struct SourceMetrics {
pub files: Vec<FileMetric>,
pub functions: Vec<FunctionMetric>,
}
pub(super) struct FileMetric {
pub path: String,
pub lines: u64,
}
pub(super) struct FunctionMetric {
pub file: String,
pub name: String,
pub kind: String,
pub start_line: u32,
pub lines: u64,
}
pub(super) fn collect(
state: &RepositoryState,
contract: &blazingly_json::Value,
include_functions: bool,
) -> Result<SourceMetrics, String> {
let mut sources = BTreeMap::<String, String>::new();
let mut files = Vec::new();
for node in state.graph().nodes() {
if node.kind != NodeKind::File || component_for(contract, &node.label).is_none() {
continue;
}
let source = read_repository_file(state.root(), &node.label)?;
files.push(FileMetric {
path: node.label.clone(),
lines: physical_lines(&source),
});
sources.insert(node.label.clone(), source);
}
files.sort_by(|left, right| left.path.cmp(&right.path));
let mut functions = Vec::new();
let mut seen = BTreeSet::new();
if include_functions {
for node in state.graph().nodes() {
if !matches!(node.kind, NodeKind::Function | NodeKind::Method) {
continue;
}
let Some(span) = node.span.as_ref() else {
continue;
};
let Some(source) = sources.get(&span.file) else {
continue;
};
let kind = node.kind.as_str().to_owned();
let key = (
span.file.clone(),
span.start.line,
kind.clone(),
node.label.clone(),
);
if !seen.insert(key) {
continue;
}
functions.push(FunctionMetric {
file: span.file.clone(),
name: node.label.clone(),
kind,
start_line: span.start.line,
lines: function_extent::lines(source, span.start.line, node.language.as_deref()),
});
}
}
functions.sort_by(|left, right| {
(&left.file, left.start_line, &left.kind, &left.name).cmp(&(
&right.file,
right.start_line,
&right.kind,
&right.name,
))
});
Ok(SourceMetrics { files, functions })
}
fn read_repository_file(root: &Path, relative: &str) -> Result<String, String> {
crate::operations::health::paths::read_contained(root, relative, "architecture source path")
}
pub(in crate::operations) fn function_lines(
source: &str,
start_line: u32,
language: Option<&str>,
) -> u64 {
function_extent::lines(source, start_line, language)
}
fn physical_lines(source: &str) -> u64 {
if source.is_empty() {
return 0;
}
let newlines = source.bytes().filter(|byte| *byte == b'\n').count();
u64::try_from(newlines + usize::from(!source.ends_with('\n'))).unwrap_or(u64::MAX)
}