use std::path::PathBuf;
use crate::node::{Node, Tree};
use crate::spaces::metrics_inner;
use crate::traits::LanguageInfo;
use crate::{
CodeMetrics, FuncSpace, LANG, Metric, MetricsOptions, ParserTrait, Source, SpaceKind, analyze,
};
fn check_func_space_with<T: ParserTrait, F: Fn(FuncSpace)>(
source: &str,
filename: &str,
options: MetricsOptions,
check: F,
) {
let path = PathBuf::from(filename);
let normalized = source.replace("\r\n", "\n").replace('\r', "\n");
let mut trimmed_bytes = normalized.trim_end().trim_matches('\n').as_bytes().to_vec();
trimmed_bytes.push(b'\n');
let parser = T::new(trimmed_bytes, &path, None);
let func_space = metrics_inner(&parser, path.to_str().map(str::to_owned), options)
.expect("metrics_inner returns Some for a parsed source");
check(func_space);
}
pub(crate) fn check_func_space<T: ParserTrait, F: Fn(FuncSpace)>(
source: &str,
filename: &str,
check: F,
) {
check_func_space_with::<T, F>(source, filename, MetricsOptions::default(), check);
}
pub(crate) fn check_func_space_only<T: ParserTrait, F: Fn(FuncSpace)>(
source: &str,
filename: &str,
metrics: &[Metric],
check: F,
) {
check_func_space_with::<T, F>(
source,
filename,
MetricsOptions::default().with_only(metrics),
check,
);
}
pub(crate) fn check_metrics_only<T: ParserTrait>(
source: &str,
filename: &str,
metrics: &[Metric],
check: fn(CodeMetrics),
) {
check_func_space_only::<T, _>(source, filename, metrics, |func_space| {
check(func_space.metrics.clone());
});
}
macro_rules! check_metrics_only_shim {
($name:ident, $($metric:ident),+ $(,)?) => {
fn $name<T: $crate::ParserTrait>(
source: &str,
filename: &str,
check: fn($crate::CodeMetrics),
) {
$crate::test_support::check_metrics_only::<T>(
source,
filename,
&[$($crate::Metric::$metric),+],
check,
);
}
};
}
macro_rules! check_func_space_only_shim {
($name:ident, $($metric:ident),+ $(,)?) => {
fn $name<T: $crate::ParserTrait, F: Fn($crate::FuncSpace)>(
source: &str,
filename: &str,
check: F,
) {
$crate::test_support::check_func_space_only::<T, F>(
source,
filename,
&[$($crate::Metric::$metric),+],
check,
);
}
};
}
pub(crate) use {check_func_space_only_shim, check_metrics_only_shim};
#[track_caller]
pub(crate) fn metrics_verbatim(lang: LANG, source: &[u8], options: MetricsOptions) -> CodeMetrics {
space_verbatim(lang, source, options).metrics.clone()
}
#[track_caller]
pub(crate) fn space_verbatim(lang: LANG, source: &[u8], options: MetricsOptions) -> FuncSpace {
analyze(Source::new(lang, source), options).expect("verbatim source must analyse")
}
#[track_caller]
pub(crate) fn parse_named(lang: LANG, name: &str, source: &str) -> crate::Ast {
crate::Ast::parse(Source::new(lang, source.as_bytes()).with_name(Some(name.to_owned())))
.expect("language feature enabled")
}
#[track_caller]
pub(crate) fn assert_fixtures_present<T>(fixtures: &[T]) {
assert!(
!fixtures.is_empty(),
"at least one language feature must be enabled for this test to mean anything"
);
}
#[track_caller]
pub(crate) fn assert_child_space_kind(func_space: &FuncSpace, name: &str, expected: SpaceKind) {
let child = child_space(func_space, name);
assert_eq!(
child.kind, expected,
"child FuncSpace {name:?} kind: got {:?}, expected {:?}",
child.kind, expected,
);
}
#[track_caller]
pub(crate) fn child_space<'a>(func_space: &'a FuncSpace, name: &str) -> &'a FuncSpace {
func_space
.spaces
.iter()
.find(|s| s.name.as_deref() == Some(name))
.unwrap_or_else(|| panic!("expected a child FuncSpace named {name:?}"))
}
#[track_caller]
pub(crate) fn function_space<'a>(func_space: &'a FuncSpace, name: &str) -> &'a FuncSpace {
let mut found: Vec<&FuncSpace> = Vec::new();
let mut stack = vec![func_space];
while let Some(space) = stack.pop() {
if space.kind == SpaceKind::Function && space.name.as_deref() == Some(name) {
found.push(space);
}
stack.extend(space.spaces.iter());
}
match found.as_slice() {
[space] => space,
other => panic!(
"expected exactly one function FuncSpace named {name:?}, found {}",
other.len()
),
}
}
pub(crate) fn for_each_node_with_chain<L: LanguageInfo>(
code: &[u8],
mut check: impl FnMut(&Node<'_>, &[Node<'_>]),
) -> usize {
let tree = Tree::new::<L>(code);
let root = tree.get_root();
assert!(
!root.has_error(),
"fixture must parse cleanly, else the walk covers error recovery"
);
let mut chain: Vec<Node<'_>> = Vec::new();
let mut stack = vec![(root, 0_usize)];
let mut visited = 0;
while let Some((node, depth)) = stack.pop() {
chain.truncate(depth);
check(&node, &chain);
visited += 1;
chain.push(node);
let first = stack.len();
stack.extend(node.children().map(|child| (child, depth + 1)));
stack[first..].reverse();
}
visited
}