#![cfg(windows)]
use std::path::{Path, PathBuf};
fn sources(root: &Path, into: &mut Vec<PathBuf>) {
let entries = std::fs::read_dir(root).unwrap_or_else(|why| panic!("read {root:?}: {why}"));
for entry in entries {
let path = entry.expect("read a directory entry").path();
if path.is_dir() {
sources(&path, into);
} else if path.extension().is_some_and(|ext| ext == "rs") {
into.push(path);
}
}
}
fn heading(content: &str) -> Option<&str> {
let rest = content.trim_start_matches('#');
let hashes = content.len() - rest.len();
if (1..=6).contains(&hashes) && rest.starts_with(' ') && !rest.trim().is_empty() {
Some(content.trim())
} else {
None
}
}
fn doc_content(line: &str) -> Option<&str> {
let trimmed = line.trim_start();
let rest = trimmed
.strip_prefix("///")
.or_else(|| trimmed.strip_prefix("//!"))?;
if rest.starts_with('/') {
return None;
}
Some(rest.strip_prefix(' ').unwrap_or(rest))
}
#[test]
fn no_doc_block_carries_the_same_heading_twice() {
let root = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
sources(&root, &mut files);
assert!(
files.len() >= 10,
"the walk found only {} source files under {root:?}, which is too few for this \
crate -- the walk is broken and every assertion below would be vacuous",
files.len()
);
let mut headings_seen = 0_usize;
let mut faults = Vec::new();
for file in &files {
let text = std::fs::read_to_string(file).expect("read a source file");
let name = file
.strip_prefix(&root)
.unwrap_or(file)
.display()
.to_string();
let mut block_start = 0_usize;
let mut in_block = false;
let mut fenced = false;
let mut found: Vec<String> = Vec::new();
for (index, line) in text.lines().enumerate() {
let Some(content) = doc_content(line) else {
in_block = false;
fenced = false;
found.clear();
continue;
};
if !in_block {
in_block = true;
block_start = index + 1;
fenced = false;
found.clear();
}
if content.trim_start().starts_with("```") {
fenced = !fenced;
continue;
}
if fenced {
continue;
}
if let Some(found_heading) = heading(content) {
headings_seen += 1;
if found.iter().any(|seen| seen == found_heading) {
faults.push(format!(
"{name}:{block_start}: the doc block carries `{found_heading}` twice \
(second at line {}), which means two comments have been fused into \
one -- the item below it is documented partly by a comment written \
for something else",
index + 1
));
} else {
found.push(found_heading.to_owned());
}
}
}
}
assert!(
headings_seen >= 20,
"only {headings_seen} headings were recognised across {} files, which is far fewer \
than this crate has -- the parser is not seeing headings, so finding no duplicates \
says nothing",
files.len()
);
assert!(
faults.is_empty(),
"{} fused doc block(s):\n{}",
faults.len(),
faults.join("\n")
);
}
#[test]
fn the_duplicate_heading_check_recognises_what_it_looks_for() {
assert_eq!(heading("# Safety"), Some("# Safety"));
assert_eq!(heading("## Panics"), Some("## Panics"));
assert_eq!(heading("###### Deep"), Some("###### Deep"));
assert_eq!(heading("Safety"), None, "prose is not a heading");
assert_eq!(heading("#NoSpace"), None, "a heading needs its space");
assert_eq!(heading("#"), None, "a bare hash is not a heading");
assert_eq!(
heading("####### Seven"),
None,
"markdown has six heading levels; seven hashes is not one of them"
);
assert_eq!(
heading("# Ok::<(), std::io::Error>(())"),
Some("# Ok::<(), std::io::Error>(())"),
"a doctest's hidden line is shaped exactly like a heading, which is why the caller \
must skip fenced regions rather than relying on this to tell them apart"
);
assert_eq!(doc_content("/// text"), Some("text"));
assert_eq!(doc_content(" //! text"), Some("text"));
assert_eq!(doc_content("///"), Some(""));
assert_eq!(
doc_content("// text"),
None,
"an ordinary comment is exempt"
);
assert_eq!(
doc_content("//// text"),
None,
"a rule of slashes is not a doc"
);
assert_eq!(doc_content("let x = 1;"), None);
}