use std::path::{Path, PathBuf};
const SELF: &str = "doctest_fence_guard.rs";
const NO_RUN: &[(&str, &str)] = &[
(
"lib.rs",
"calls `Application::run`, which takes over the thread and opens a real window",
),
(
"elements/dialog.rs",
"calls `Application::run`, which takes over the thread and opens a real window",
),
(
"elements/toast.rs",
"calls `Application::run`, which takes over the thread and opens a real window",
),
(
"markdown/code_highlight.rs",
"calls `Application::run`, which takes over the thread and opens a real window",
),
];
const COMPILE_FAIL: &[(&str, &str)] = &[];
const ALWAYS_ALLOWED: &[&str] = &["", "rust", "text"];
fn repo_root() -> PathBuf {
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
}
fn rust_files(dir: &Path) -> Vec<PathBuf> {
let mut files = Vec::new();
let mut stack = vec![dir.to_path_buf()];
while let Some(dir) = stack.pop() {
for entry in std::fs::read_dir(&dir).expect("source directory is readable") {
let path = entry.expect("directory entry is readable").path();
if path.is_dir() {
stack.push(path);
} else if path.extension().is_some_and(|ext| ext == "rs") {
files.push(path);
}
}
}
files.sort();
files
}
fn doc_fences(source: &str) -> Vec<(usize, String)> {
let mut fences = Vec::new();
let mut open = false;
for (index, line) in source.lines().enumerate() {
let trimmed = line.trim_start();
let Some(rest) = trimmed
.strip_prefix("//!")
.or_else(|| trimmed.strip_prefix("///"))
else {
continue;
};
let Some(after) = rest.trim_start().strip_prefix("```") else {
continue;
};
if open {
open = false;
continue;
}
open = true;
fences.push((index + 1, after.trim().to_string()));
}
fences
}
#[test]
fn every_doc_example_is_one_rustdoc_will_compile() {
let src = repo_root().join("src");
let files = rust_files(&src);
assert!(
files.len() > 20,
"walked src/ and found {} Rust files — the walker is broken",
files.len(),
);
let mut scanned = 0usize;
let mut fences_seen = 0usize;
let mut no_run_seen: Vec<String> = Vec::new();
let mut compile_fail_seen: Vec<String> = Vec::new();
for file in &files {
if file.file_name().is_some_and(|name| name == SELF) {
continue;
}
scanned += 1;
let relative = file
.strip_prefix(&src)
.expect("file is under src/")
.to_str()
.expect("source paths are UTF-8")
.to_string();
let source = std::fs::read_to_string(file).expect("source file is readable");
for (line, fence) in doc_fences(&source) {
fences_seen += 1;
let at = format!("{relative}:{line}");
assert_ne!(
fence, "ignore",
"{at} is a ```ignore example. rustdoc never compiles one, so nothing \
keeps it true — this is the fence that let `Application::new()` sit \
in the crate's own Quick Start after it stopped existing. There is no \
allowlist for it. Make the example compile (see this module's docs for \
the hidden prelude), mark it `no_run` and add an entry to `NO_RUN` in \
{SELF} if running it opens a window, or delete it — an example whose \
prose was doing the work does not lose anything by going."
);
if ALWAYS_ALLOWED.contains(&fence.as_str()) {
continue;
}
if fence == "no_run" {
assert!(
NO_RUN.iter().any(|(path, _)| *path == relative),
"{at} is a ```no_run example, but {relative} has no entry in `NO_RUN` \
in {SELF}. `no_run` costs the one thing a doctest is for — running \
— so add an entry saying why running it is not an option, or make \
it run."
);
no_run_seen.push(relative.clone());
continue;
}
if fence == "compile_fail" {
assert!(
COMPILE_FAIL.iter().any(|(path, _)| *path == relative),
"{at} is a ```compile_fail example, but {relative} has no entry in \
`COMPILE_FAIL` in {SELF}. rustdoc does check these, but it cannot \
merge one into the shared doctest binary, so each is a whole-program \
link of gpui — see gpuikit#180. Add an entry saying the link is \
worth it."
);
compile_fail_seen.push(relative.clone());
continue;
}
panic!(
"{at} carries the fence ```{fence}, which this crate has no rule for. \
Either it is a typo, or it is a rustdoc attribute worth a deliberate \
decision — add it to `ALWAYS_ALLOWED` or give it an allowlist in {SELF}."
);
}
}
assert_eq!(
scanned,
files.len() - 1,
"exactly one file — `{SELF}`, which names every fence in its own prose — is \
exempt from this scan. {} were skipped.",
files.len() - scanned,
);
assert!(
fences_seen > 30,
"found only {fences_seen} doc-comment fences in src/ — the scanner is reading \
nothing, and its 'no ```ignore anywhere' verdict means nothing either",
);
for (path, justification) in NO_RUN {
assert!(
no_run_seen.iter().any(|seen| seen == path),
"`NO_RUN` in {SELF} still allows `{path}` ({justification}), but that file \
has no ```no_run example any more. Delete the entry.",
);
}
for (path, justification) in COMPILE_FAIL {
assert!(
compile_fail_seen.iter().any(|seen| seen == path),
"`COMPILE_FAIL` in {SELF} still allows `{path}` ({justification}), but that \
file has no ```compile_fail example any more. Delete the entry.",
);
}
}
#[test]
fn the_scanner_reads_doc_fences_and_only_doc_fences() {
let source = r#"
//! ```
//! let module_level = 1;
//! ```
//!
//! ```text
//! not rust
//! ```
// ``` a plain comment fence is not a doc example
/// ```no_run
/// let item_level = 2;
/// ```
fn documented() {
let s = "``` a fence inside a string literal";
}
"#;
assert_eq!(
doc_fences(source),
vec![
(2, String::new()),
(6, "text".to_string()),
(11, "no_run".to_string()),
],
"the scanner should report three openers — bare, text, no_run — and neither \
the closers, the `//` comment, nor the string literal",
);
}