#![allow(
clippy::unwrap_used,
clippy::expect_used,
clippy::indexing_slicing,
clippy::panic
)]
use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
const NOT_CALLED: &[(&str, &str)] = &[
(
"index_into",
"`#[doc(hidden)]` method of the sealed `ValueIndex` trait; used through `Value::get` and `value[..]`",
),
(
"index_into_mut",
"`#[doc(hidden)]` method of the sealed `ValueIndex` trait; used through `Value::get_mut`",
),
(
"same_allocation",
"`#[doc(hidden)]` test hook of `Tag` behind the unstable `__internal-checks` feature, outside the stability promise",
),
(
"shares_prefix_with",
"`#[doc(hidden)]` test hook of `Tag` behind the unstable `__internal-checks` feature, outside the stability promise",
),
(
"push_segment",
"`oneyaml_core::__private` (`#[doc(hidden)]`): support for the `path!` macro, used through it",
),
];
fn root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn core_src() -> PathBuf {
root().join("../oneyaml-core/src")
}
fn rs_files(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else {
return;
};
let mut entries: Vec<PathBuf> = rd.map(|e| e.unwrap().path()).collect();
entries.sort();
for p in entries {
if p.is_dir() {
rs_files(&p, out);
} else if p.extension().is_some_and(|e| e == "rs") {
out.push(p);
}
}
}
fn pub_fns(files: &[PathBuf]) -> BTreeSet<String> {
let mut out = BTreeSet::new();
for f in files {
if f.file_name().is_some_and(|n| n == "tests.rs") {
continue;
}
let text = std::fs::read_to_string(f).unwrap();
let mut in_pub_trait = false;
for line in text.lines() {
if line.starts_with("pub trait ") {
in_pub_trait = true;
} else if line.starts_with('}') {
in_pub_trait = false;
}
let t = line.trim_start();
let rest = t
.strip_prefix("pub fn ")
.or_else(|| t.strip_prefix("pub const fn "))
.or_else(|| t.strip_prefix("fn ").filter(|_| in_pub_trait));
if let Some(rest) = rest {
let name: String = rest
.chars()
.take_while(|c| c.is_alphanumeric() || *c == '_')
.collect();
out.insert(name);
}
}
}
out
}
fn doc_code(text: &str, markdown: bool) -> String {
let mut code = String::new();
let mut inside = false;
for line in text.lines() {
let t = line.trim_start();
let body = if markdown {
Some(t)
} else {
t.strip_prefix("///").or_else(|| t.strip_prefix("//!"))
};
let Some(body) = body else {
inside = false;
continue;
};
let body = body.trim_start();
if body.starts_with("```") {
inside = !inside;
continue;
}
if inside {
code.push_str(body);
code.push('\n');
}
}
code
}
fn called(code: &str, name: &str) -> bool {
let pat = format!("{name}(");
code.match_indices(&pat).any(|(i, _)| {
code[..i]
.chars()
.next_back()
.is_none_or(|c| !(c.is_alphanumeric() || c == '_'))
})
}
fn example_code() -> String {
let src = root().join("src");
let mut code = String::new();
let mut examples = Vec::new();
rs_files(&root().join("examples"), &mut examples);
for f in &examples {
code.push_str(&std::fs::read_to_string(f).unwrap());
}
let mut all_src = Vec::new();
rs_files(&src, &mut all_src);
rs_files(&core_src(), &mut all_src);
for f in &all_src {
code.push_str(&doc_code(&std::fs::read_to_string(f).unwrap(), false));
}
code.push_str(&doc_code(
&std::fs::read_to_string(root().join("README.md")).unwrap(),
true,
));
code
}
const CHAINED: &[&str] = &[
"set",
"set_scalar",
"set_anchor",
"set_leading",
"set_trailing",
"push_footer",
"clear_comments",
"set_blank_lines_before",
"into_key",
"push",
"insert",
"insert_at",
"replace_at",
"push_alias",
"insert_alias",
"insert_merge",
"insert_removed",
"insert_removed_at",
];
fn chained(code: &str, name: &str) -> bool {
let pat = format!(".{name}(");
code.match_indices(&pat).any(|(i, _)| {
let before = code[..i].trim_end();
before.ends_with('?') || before.ends_with(".unwrap()")
})
}
#[test]
fn node_mut_edits_are_used_through_handles() {
let code = example_code();
let edit = std::fs::read_to_string(root().join("src/document/edit.rs")).unwrap();
let missing: Vec<&str> = CHAINED
.iter()
.copied()
.filter(|n| !chained(&code, n))
.collect();
assert!(
missing.is_empty(),
"NodeMut edits used on no handle in an example: {missing:?}"
);
let node_mut = edit
.split("impl<'d> NodeMut<'d> {")
.nth(1)
.and_then(|rest| rest.split("\n}\n").next())
.unwrap();
for n in CHAINED {
assert!(
node_mut.contains(&format!("pub fn {n}("))
|| node_mut.contains(&format!("pub fn {n}<")),
"{n} is not a NodeMut method"
);
}
}
#[test]
fn every_public_fn_is_used_in_an_example() {
let src = root().join("src");
let mut declared = Vec::new();
rs_files(&src, &mut declared);
for file in [
"document/edit.rs",
"document/view.rs",
"document/key.rs",
"ser/mod.rs",
"de/api.rs",
"de/value.rs",
"value/json.rs",
"error.rs",
"render.rs",
"miette_impl.rs",
"spanned.rs",
] {
let f = src.join(file);
assert!(declared.contains(&f), "{}", f.display());
}
let in_workspace = core_src().is_dir();
if in_workspace {
rs_files(&core_src(), &mut declared);
} else {
eprintln!(
"{} not found: oneyaml-core not checked",
core_src().display()
);
}
let names = pub_fns(&declared);
let floor = if in_workspace { 250 } else { 150 };
assert!(names.len() > floor, "found {} names", names.len());
let code = example_code();
let exempt: BTreeSet<&str> = NOT_CALLED.iter().map(|(n, _)| *n).collect();
let missing: Vec<&String> = names
.iter()
.filter(|n| !exempt.contains(n.as_str()) && !called(&code, n))
.collect();
assert!(
missing.is_empty(),
"public functions used in no example or doc example: {missing:?}"
);
for (n, _) in NOT_CALLED {
assert!(names.contains(*n) || !in_workspace, "stale exemption {n}");
}
}
#[test]
fn test_only_checks_are_behind_the_internal_feature() {
let gated = [
("src/document/lookup.rs", "check_lookup_indexes"),
("src/value/tag.rs", "same_allocation"),
("src/value/tag.rs", "shares_prefix_with"),
];
for (file, name) in gated {
let text = std::fs::read_to_string(root().join(file)).unwrap();
let lines: Vec<&str> = text.lines().collect();
let at = lines
.iter()
.position(|l| l.trim_start().starts_with(&format!("pub fn {name}(")))
.unwrap_or_else(|| panic!("{name} not found in {file}"));
let gate = lines[..at]
.iter()
.rev()
.take_while(|l| {
let l = l.trim_start();
l.starts_with("#[") || l.starts_with("///")
})
.any(|l| l.trim() == "#[cfg(feature = \"__internal-checks\")]");
assert!(gate, "{name} is not behind `__internal-checks`");
}
}