1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
//! A timing is never a declaration of its own.
//!
//! The seventeen `animate_*` and `keyframes_*` builders that `Container` used
//! to carry each named a property and said how it moved, two lines away from
//! where the property's value was set. Nothing checked that the two agreed,
//! and nothing stopped a timing being declared for a property that was never
//! set at all. They are gone: a motion now rides with the value, so there is
//! no way to spell one without naming what it moves.
//!
//! This reads the source the way `documentation_references.rs` does, because
//! the property being asserted is the *absence* of an API — nothing can call
//! what is not there, so no ordinary test can watch for its return.
use std::path::{Path, PathBuf};
/// The one `pub fn animate_*` that is not a declaration, named with the file it
/// lives in so that the exemption does not quietly cover a second one
/// somewhere else: `AnimationState::animate_to` retargets a running animation
/// from inside the advance loop, and the value it is given is the one it moves
/// to.
const NOT_A_DECLARATION: &[(&str, &str)] = &[("widgets/container/animations.rs", "animate_to")];
fn rust_files(dir: &Path, 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() {
rust_files(&path, out);
} else if path.extension().is_some_and(|e| e == "rs") {
out.push(path);
}
}
}
/// The name a `pub fn` line declares, if the line declares one.
fn public_fn_name(line: &str) -> Option<&str> {
let rest = line.trim().strip_prefix("pub fn ")?;
let end = rest.find(|c: char| !c.is_alphanumeric() && c != '_')?;
Some(&rest[..end])
}
#[test]
fn no_public_builder_declares_a_timing_beside_its_property() {
let src = Path::new(env!("CARGO_MANIFEST_DIR")).join("src");
let mut files = Vec::new();
rust_files(&src, &mut files);
let mut found: Vec<String> = Vec::new();
for file in &files {
let text = std::fs::read_to_string(file).expect("a source file this crate owns");
for line in text.lines() {
let Some(name) = public_fn_name(line) else {
continue;
};
if !name.starts_with("animate_") && !name.starts_with("keyframes_") {
continue;
}
let shown = file
.strip_prefix(&src)
.unwrap_or(file)
.display()
.to_string();
if NOT_A_DECLARATION.contains(&(shown.as_str(), name)) {
continue;
}
found.push(format!("{shown}: {name}"));
}
}
found.sort();
assert!(
found.is_empty(),
"a motion rides with the value it moves, so no builder names a \
property and a timing apart. Found:\n {}",
found.join("\n ")
);
}