Skip to content

Commit b631f0a

Browse files
authored
Unrolled build for #141709
Rollup merge of #141709 - aDotInTheVoid:split-for-docs, r=GuillaumeGomez jsondocck: Refactor directive handling Best reviewed commit by commit. 1. Moves directive handling into its own file. This makes it easier to link to in the dev-guide (rust-lang/rustc-dev-guide#2422 (comment)), but also makes the code nicer in it's own right 2. Renames command to directive. This is what compiletest uses, and it's nice to not have 2 words for this. r? ``@GuillaumeGomez``
2 parents 0b20963 + 91ad4bf commit b631f0a

File tree

4 files changed

+251
-238
lines changed

4 files changed

+251
-238
lines changed

src/tools/jsondocck/src/config.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ use getopts::Options;
44
pub struct Config {
55
/// The directory documentation output was generated in
66
pub doc_dir: String,
7-
/// The file documentation was generated for, with docck commands to check
7+
/// The file documentation was generated for, with docck directives to check
88
pub template: String,
99
}
1010

src/tools/jsondocck/src/directive.rs

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
use std::borrow::Cow;
2+
3+
use serde_json::Value;
4+
5+
use crate::cache::Cache;
6+
7+
#[derive(Debug)]
8+
pub struct Directive {
9+
pub kind: DirectiveKind,
10+
pub path: String,
11+
pub lineno: usize,
12+
}
13+
14+
#[derive(Debug)]
15+
pub enum DirectiveKind {
16+
/// `//@ has <path>`
17+
///
18+
/// Checks the path exists.
19+
HasPath,
20+
21+
/// `//@ has <path> <value>`
22+
///
23+
/// Check one thing at the path is equal to the value.
24+
HasValue { value: String },
25+
26+
/// `//@ !has <path>`
27+
///
28+
/// Checks the path doesn't exist.
29+
HasNotPath,
30+
31+
/// `//@ !has <path> <value>`
32+
///
33+
/// Checks the path exists, but doesn't have the given value.
34+
HasNotValue { value: String },
35+
36+
/// `//@ is <path> <value>`
37+
///
38+
/// Check the path is the given value.
39+
Is { value: String },
40+
41+
/// `//@ is <path> <value> <value>...`
42+
///
43+
/// Check that the path matches to exactly every given value.
44+
IsMany { values: Vec<String> },
45+
46+
/// `//@ !is <path> <value>`
47+
///
48+
/// Check the path isn't the given value.
49+
IsNot { value: String },
50+
51+
/// `//@ count <path> <value>`
52+
///
53+
/// Check the path has the expected number of matches.
54+
CountIs { expected: usize },
55+
56+
/// `//@ set <name> = <path>`
57+
Set { variable: String },
58+
}
59+
60+
impl DirectiveKind {
61+
/// Returns both the kind and the path.
62+
///
63+
/// Returns `None` if the directive isn't from jsondocck (e.g. from compiletest).
64+
pub fn parse<'a>(
65+
directive_name: &str,
66+
negated: bool,
67+
args: &'a [String],
68+
) -> Option<(Self, &'a str)> {
69+
let kind = match (directive_name, negated) {
70+
("count", false) => {
71+
assert_eq!(args.len(), 2);
72+
let expected = args[1].parse().expect("invalid number for `count`");
73+
Self::CountIs { expected }
74+
}
75+
76+
("ismany", false) => {
77+
// FIXME: Make this >= 3, and migrate len(values)==1 cases to @is
78+
assert!(args.len() >= 2, "Not enough args to `ismany`");
79+
let values = args[1..].to_owned();
80+
Self::IsMany { values }
81+
}
82+
83+
("is", false) => {
84+
assert_eq!(args.len(), 2);
85+
Self::Is { value: args[1].clone() }
86+
}
87+
("is", true) => {
88+
assert_eq!(args.len(), 2);
89+
Self::IsNot { value: args[1].clone() }
90+
}
91+
92+
("set", false) => {
93+
assert_eq!(args.len(), 3);
94+
assert_eq!(args[1], "=");
95+
return Some((Self::Set { variable: args[0].clone() }, &args[2]));
96+
}
97+
98+
("has", false) => match args {
99+
[_path] => Self::HasPath,
100+
[_path, value] => Self::HasValue { value: value.clone() },
101+
_ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"),
102+
},
103+
("has", true) => match args {
104+
[_path] => Self::HasNotPath,
105+
[_path, value] => Self::HasNotValue { value: value.clone() },
106+
_ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"),
107+
},
108+
// Ignore compiletest directives, like //@ edition
109+
(_, false) if KNOWN_DIRECTIVE_NAMES.contains(&directive_name) => {
110+
return None;
111+
}
112+
_ => {
113+
panic!("Invalid directive `//@ {}{directive_name}`", if negated { "!" } else { "" })
114+
}
115+
};
116+
117+
Some((kind, &args[0]))
118+
}
119+
}
120+
121+
impl Directive {
122+
/// Performs the actual work of ensuring a directive passes.
123+
pub fn check(&self, cache: &mut Cache) -> Result<(), String> {
124+
let matches = cache.select(&self.path);
125+
match &self.kind {
126+
DirectiveKind::HasPath => {
127+
if matches.is_empty() {
128+
return Err("matched to no values".to_owned());
129+
}
130+
}
131+
DirectiveKind::HasNotPath => {
132+
if !matches.is_empty() {
133+
return Err(format!("matched to {matches:?}, but wanted no matches"));
134+
}
135+
}
136+
DirectiveKind::HasValue { value } => {
137+
let want_value = string_to_value(value, cache);
138+
if !matches.contains(&want_value.as_ref()) {
139+
return Err(format!(
140+
"matched to {matches:?}, which didn't contain {want_value:?}"
141+
));
142+
}
143+
}
144+
DirectiveKind::HasNotValue { value } => {
145+
let wantnt_value = string_to_value(value, cache);
146+
if matches.contains(&wantnt_value.as_ref()) {
147+
return Err(format!(
148+
"matched to {matches:?}, which contains unwanted {wantnt_value:?}"
149+
));
150+
} else if matches.is_empty() {
151+
return Err(format!(
152+
"got no matches, but expected some matched (not containing {wantnt_value:?}"
153+
));
154+
}
155+
}
156+
157+
DirectiveKind::Is { value } => {
158+
let want_value = string_to_value(value, cache);
159+
let matched = get_one(&matches)?;
160+
if matched != want_value.as_ref() {
161+
return Err(format!("matched to {matched:?} but want {want_value:?}"));
162+
}
163+
}
164+
DirectiveKind::IsNot { value } => {
165+
let wantnt_value = string_to_value(value, cache);
166+
let matched = get_one(&matches)?;
167+
if matched == wantnt_value.as_ref() {
168+
return Err(format!("got value {wantnt_value:?}, but want anything else"));
169+
}
170+
}
171+
172+
DirectiveKind::IsMany { values } => {
173+
// Serde json doesn't implement Ord or Hash for Value, so we must
174+
// use a Vec here. While in theory that makes setwize equality
175+
// O(n^2), in practice n will never be large enough to matter.
176+
let expected_values =
177+
values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>();
178+
if expected_values.len() != matches.len() {
179+
return Err(format!(
180+
"Expected {} values, but matched to {} values ({:?})",
181+
expected_values.len(),
182+
matches.len(),
183+
matches
184+
));
185+
};
186+
for got_value in matches {
187+
if !expected_values.iter().any(|exp| &**exp == got_value) {
188+
return Err(format!("has match {got_value:?}, which was not expected",));
189+
}
190+
}
191+
}
192+
DirectiveKind::CountIs { expected } => {
193+
if *expected != matches.len() {
194+
return Err(format!(
195+
"matched to `{matches:?}` with length {}, but expected length {expected}",
196+
matches.len(),
197+
));
198+
}
199+
}
200+
DirectiveKind::Set { variable } => {
201+
let value = get_one(&matches)?;
202+
let r = cache.variables.insert(variable.to_owned(), value.clone());
203+
assert!(r.is_none(), "name collision: {variable:?} is duplicated");
204+
}
205+
}
206+
207+
Ok(())
208+
}
209+
}
210+
211+
fn get_one<'a>(matches: &[&'a Value]) -> Result<&'a Value, String> {
212+
match matches {
213+
[] => Err("matched to no values".to_owned()),
214+
[matched] => Ok(matched),
215+
_ => Err(format!("matched to multiple values {matches:?}, but want exactly 1")),
216+
}
217+
}
218+
219+
// FIXME: This setup is temporary until we figure out how to improve this situation.
220+
// See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>.
221+
include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../compiletest/src/directive-list.rs"));
222+
223+
fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
224+
if s.starts_with("$") {
225+
Cow::Borrowed(&cache.variables.get(&s[1..]).unwrap_or_else(|| {
226+
// FIXME(adotinthevoid): Show line number
227+
panic!("No variable: `{}`. Current state: `{:?}`", &s[1..], cache.variables)
228+
}))
229+
} else {
230+
Cow::Owned(serde_json::from_str(s).expect(&format!("Cannot convert `{}` to json", s)))
231+
}
232+
}

src/tools/jsondocck/src/error.rs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
1-
use crate::Command;
1+
use crate::Directive;
22

33
#[derive(Debug)]
44
pub struct CkError {
55
pub message: String,
6-
pub command: Command,
6+
pub directive: Directive,
77
}

0 commit comments

Comments
 (0)