use std::collections::BTreeSet;
use std::path::{Path, PathBuf};
const SHORTEST_NAME: usize = 3;
const NOT_CRATE_SYMBOLS: &[&str] = &[
"grim",
"reviewer",
"Button",
"rotation_signal",
"mdbook",
"lavapipe",
"llvmpipe",
"VK_ICD_FILENAMES",
"WAYLAND_DISPLAY",
"vulkan-swrast",
"mesa-vulkan-drivers",
];
fn repo() -> &'static Path {
Path::new(env!("CARGO_MANIFEST_DIR"))
}
fn read_dir_files(dir: &Path, extension: &str, out: &mut Vec<PathBuf>) {
let Ok(entries) = std::fs::read_dir(dir) else {
return;
};
for entry in entries.flatten() {
let path = entry.path();
if path.is_dir() {
read_dir_files(&path, extension, out);
} else if path.extension().is_some_and(|e| e == extension) {
out.push(path);
}
}
}
fn crate_source() -> String {
let mut files = Vec::new();
read_dir_files(&repo().join("src"), "rs", &mut files);
read_dir_files(&repo().join("guido-macros/src"), "rs", &mut files);
read_dir_files(&repo().join("tests"), "rs", &mut files);
let mut text = files
.iter()
.filter_map(|p| std::fs::read_to_string(p).ok())
.collect::<Vec<_>>()
.join("\n");
if let Ok(entries) = std::fs::read_dir(repo().join("examples")) {
for entry in entries.flatten() {
if let Some(stem) = entry.path().file_stem() {
text.push('\n');
text.push_str(&stem.to_string_lossy());
}
}
}
text
}
fn backticked_identifiers(text: &str) -> BTreeSet<String> {
let mut found = BTreeSet::new();
let mut rest = text;
while let Some(start) = rest.find("```") {
scan_spans(&rest[..start], &mut found);
let after = &rest[start + 3..];
match after.find("```") {
Some(end) => rest = &after[end + 3..],
None => return found,
}
}
scan_spans(rest, &mut found);
found
}
fn scan_spans(text: &str, found: &mut BTreeSet<String>) {
let mut parts = text.split('`');
parts.next();
while let Some(inside) = parts.next() {
let candidate = inside.strip_suffix("()").unwrap_or(inside);
let looks_like_code = candidate.len() >= 3
&& candidate
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '_' || c == ':')
&& candidate.chars().any(|c| c.is_ascii_alphabetic());
if looks_like_code {
found.insert(candidate.to_string());
}
parts.next();
}
}
#[test]
fn every_identifier_the_agent_documentation_names_still_exists() {
let source = crate_source();
let mut docs = vec![repo().join("AGENTS.md")];
for dir in [".claude/skills", ".claude/commands", ".claude/agents"] {
let mut found = Vec::new();
read_dir_files(&repo().join(dir), "md", &mut found);
assert!(
!found.is_empty(),
"no documentation found under {dir} — this test is checking nothing"
);
docs.extend(found);
}
let mut stale = Vec::new();
let mut checked = 0usize;
for doc in &docs {
let text = std::fs::read_to_string(doc).expect("read documentation");
for identifier in backticked_identifiers(&text) {
if NOT_CRATE_SYMBOLS.contains(&identifier.as_str()) {
continue;
}
let leaf = identifier.rsplit("::").next().unwrap_or(&identifier);
if leaf.len() < SHORTEST_NAME {
continue;
}
checked += 1;
if !contains_word(&source, leaf) {
let name = doc
.strip_prefix(repo())
.unwrap_or(doc)
.display()
.to_string();
stale.push(format!(" {name}: `{identifier}`"));
}
}
}
assert!(
stale.is_empty(),
"documentation names {} identifier(s) the crate does not have. Either \
the documentation went stale when something was renamed, or the name \
belongs in NOT_CRATE_SYMBOLS in this file:\n{}\n\n({checked} \
identifiers checked)",
stale.len(),
stale.join("\n")
);
}
fn contains_word(haystack: &str, word: &str) -> bool {
let bytes = haystack.as_bytes();
let mut from = 0;
while let Some(offset) = haystack[from..].find(word) {
let start = from + offset;
let end = start + word.len();
let before_ok = start == 0 || !is_word_byte(bytes[start - 1]);
let after_ok = end == bytes.len() || !is_word_byte(bytes[end]);
if before_ok && after_ok {
return true;
}
from = start + 1;
}
false
}
fn is_word_byte(b: u8) -> bool {
b.is_ascii_alphanumeric() || b == b'_'
}
#[test]
fn every_identifier_the_user_documentation_names_still_exists() {
let source = crate_source();
let mut docs = vec![repo().join("README.md")];
for dir in ["docs", "book/src"] {
let mut found = Vec::new();
read_dir_files(&repo().join(dir), "md", &mut found);
assert!(
!found.is_empty(),
"no markdown under {dir}: checking nothing"
);
docs.extend(found);
}
let mut stale = Vec::new();
let mut checked = 0usize;
for doc in &docs {
let text = std::fs::read_to_string(doc).expect("read documentation");
for identifier in backticked_identifiers(&text) {
if NOT_CRATE_SYMBOLS.contains(&identifier.as_str()) {
continue;
}
let leaf = identifier.rsplit("::").next().unwrap_or(&identifier);
if leaf.len() < SHORTEST_NAME {
continue;
}
checked += 1;
if !contains_word(&source, leaf) {
let name = doc.strip_prefix(repo()).unwrap_or(doc).display();
stale.push(format!(" {name}: `{identifier}`"));
}
}
}
assert!(
stale.is_empty(),
"the user documentation names {} identifier(s) the crate does not \
have. Either it went stale when something was renamed, or the name \
belongs in NOT_CRATE_SYMBOLS in this file:\n{}\n\n({checked} \
identifiers checked)",
stale.len(),
stale.join("\n")
);
}
#[test]
fn every_fence_in_the_book_says_what_it_holds() {
const ALLOWED: [&str; 6] = ["rust", "rust,ignore", "rust,no_run", "text", "bash", "toml"];
const ALSO: [&str; 1] = ["wgsl"];
let mut wrong = Vec::new();
let mut checked = 0usize;
for (path, source) in book_pages() {
let name = path.strip_prefix(repo()).unwrap_or(&path).display();
for (line, info, _) in fences(&source) {
checked += 1;
if ALLOWED.contains(&info) || ALSO.contains(&info) {
continue;
}
wrong.push(format!(" {name}:{line}: ```{info}"));
}
}
assert!(
wrong.is_empty(),
"{} fence(s) in the book carry an info string rustdoc does not know, so \
`mdbook test` skips them without saying so. An empty one counts: mdbook \
gives it to rustdoc as Rust.\n{}\n\n({checked} fences checked)",
wrong.len(),
wrong.join("\n")
);
}
const REASON: &str = "// not compiled:";
fn book_pages() -> Vec<(PathBuf, String)> {
let mut pages = Vec::new();
read_dir_files(&repo().join("book/src"), "md", &mut pages);
assert!(
!pages.is_empty(),
"no book chapters found: scanning nothing"
);
pages.sort();
pages
.into_iter()
.map(|path| {
let source = std::fs::read_to_string(&path).expect("unreadable chapter");
(path, source)
})
.collect()
}
fn fences(source: &str) -> Vec<(usize, &str, Vec<&str>)> {
let lines: Vec<&str> = source.lines().collect();
let mut found = Vec::new();
let mut index = 0;
while index < lines.len() {
if !lines[index].starts_with("```") {
index += 1;
continue;
}
let info = lines[index][3..].trim();
let opened = index + 1;
index += 1;
let mut body = Vec::new();
while index < lines.len() && !lines[index].starts_with("```") {
body.push(lines[index]);
index += 1;
}
found.push((opened, info, body));
index += 1;
}
found
}
fn is_ignored(info: &str) -> bool {
info.split(',').any(|token| token.trim() == "ignore")
}
#[test]
fn every_ignored_sample_in_the_book_says_why_not() {
let mut unexplained = Vec::new();
let (mut exempt, mut by_chapter) = (0usize, 0usize);
for (path, source) in book_pages() {
let name = path.strip_prefix(repo()).unwrap_or(&path).display();
let internals = path.components().any(|c| c.as_os_str() == "architecture");
for (line, info, body) in fences(&source) {
if !is_ignored(info) {
continue;
}
if internals {
by_chapter += 1;
continue;
}
let first = body.first().copied().unwrap_or_default().trim_start();
let first = first.strip_prefix("# ").unwrap_or(first);
if first.starts_with(REASON) {
exempt += 1;
} else {
unexplained.push(format!(" {name}:{line}"));
}
}
}
assert!(
unexplained.is_empty(),
"{} sample(s) in the book are marked `ignore`, so `mdbook test` does not \
compile them and nothing else checks the names inside them. Either drop \
the `ignore` — adding whatever hidden `#` setup lines it takes, or \
`no_run` where the sample compiles but must not open a surface — or \
state the reason on the block's first line as `# {REASON} ...`.\n{}\n\n\
({exempt} exempt with a reason, {by_chapter} in architecture/)",
unexplained.len(),
unexplained.join("\n")
);
}
#[test]
fn the_ignored_share_the_documentation_quotes_is_the_real_one() {
let (mut rust, mut ignored) = (0usize, 0usize);
for (_, source) in book_pages() {
for (_, info, _) in fences(&source) {
if !info.starts_with("rust") {
continue;
}
rust += 1;
if is_ignored(info) {
ignored += 1;
}
}
}
assert!(rust > 0, "no Rust fences in the book: counting nothing");
let share = (ignored * 100 + rust / 2) / rust;
for doc in [
repo().join("AGENTS.md"),
repo().join(".claude/skills/book/SKILL.md"),
] {
let source = std::fs::read_to_string(&doc).expect("unreadable documentation");
let name = doc.strip_prefix(repo()).unwrap_or(&doc).display();
let quoted = quoted_ignored_shares(&source);
assert!(
!quoted.is_empty(),
"{name} no longer says what share of the book is `ignore`d. It is \
{share}% ({ignored} of {rust} Rust blocks), and the reader of that \
file decides whether to grep by hand on the strength of it."
);
for (line, percent) in quoted {
assert_eq!(
percent, share,
"{name}:{line} says {percent}% of the book is `ignore`d. It is \
{share}% — {ignored} of {rust} Rust blocks — as of this run."
);
}
}
}
fn quoted_ignored_shares(source: &str) -> Vec<(usize, usize)> {
let mut found = Vec::new();
let mut line = 1usize;
for paragraph in source.split("\n\n") {
let first_line = line;
line += paragraph.matches('\n').count() + 2;
let flowed = paragraph.replace('\n', " ");
let mut rest = flowed.as_str();
while let Some(at) = rest.find('%') {
let start = rest[..at]
.rfind(|c: char| !c.is_ascii_digit())
.map_or(0, |end| end + 1);
let near: String = rest[at..].chars().take(40).collect();
if let Ok(percent) = rest[start..at].parse::<usize>()
&& near.contains("`ignore`")
{
found.push((first_line, percent));
}
rest = &rest[at + 1..];
}
}
found
}
#[test]
fn every_rustdoc_sample_is_compiled_or_says_why_not() {
let mut sources = Vec::new();
read_dir_files(&repo().join("src"), "rs", &mut sources);
read_dir_files(&repo().join("guido-macros/src"), "rs", &mut sources);
assert!(!sources.is_empty(), "no sources found: scanning nothing");
let mut unexplained = Vec::new();
let mut exempt = 0usize;
for file in sources {
let source = std::fs::read_to_string(&file).expect("unreadable source");
let lines: Vec<&str> = source.lines().collect();
for (number, line) in lines.iter().enumerate() {
let Some(fence) = doc_comment_body(line) else {
continue;
};
let Some(info) = fence.trim().strip_prefix("```") else {
continue;
};
if !is_ignored(info) {
continue;
}
let first = lines
.get(number + 1)
.and_then(|next| doc_comment_body(next))
.unwrap_or_default();
if first.trim_start().starts_with(REASON) {
exempt += 1;
continue;
}
let name = file.strip_prefix(repo()).unwrap_or(&file).display();
unexplained.push(format!(" {name}:{}", number + 1));
}
}
assert!(
unexplained.is_empty(),
"{} rustdoc sample(s) are marked `ignore`, so nothing compiles them and \
nothing else checks the names inside them. Either drop the `ignore` — \
adding whatever hidden `#` setup lines it takes, or `no_run` where the \
sample compiles but must not run — or state the reason on the block's \
first line as `{REASON} ...`.\n{}\n\n({exempt} exempt with a reason)",
unexplained.len(),
unexplained.join("\n")
);
}
fn doc_comment_body(line: &str) -> Option<&str> {
let trimmed = line.trim_start();
let body = trimmed
.strip_prefix("///")
.or_else(|| trimmed.strip_prefix("//!"))?;
Some(body.strip_prefix(' ').unwrap_or(body))
}