diff --git a/src/tools/jsondoclint/src/json_find.rs b/src/tools/jsondoclint/src/json_find.rs new file mode 100644 index 000000000000..95ea8866609d --- /dev/null +++ b/src/tools/jsondoclint/src/json_find.rs @@ -0,0 +1,74 @@ +use std::fmt::Write; + +use serde_json::Value; + +#[derive(Debug, Clone)] +pub enum SelectorPart { + Field(String), + Index(usize), +} + +pub type Selector = Vec; + +pub fn to_jsonpath(sel: &Selector) -> String { + let mut s = String::from("$"); + for part in sel { + match part { + SelectorPart::Field(name) => { + if is_jsonpath_safe(name) { + write!(&mut s, ".{}", name).unwrap(); + } else { + // This is probably wrong in edge cases, but all Id's are + // just ascii alphanumerics, `-` `_`, and `:` + write!(&mut s, "[{name:?}]").unwrap(); + } + } + SelectorPart::Index(idx) => write!(&mut s, "[{idx}]").unwrap(), + } + } + s +} + +fn is_jsonpath_safe(s: &str) -> bool { + s.chars().all(|c| c.is_ascii_alphanumeric() || c == '_') +} + +pub fn find_selector(haystack: &Value, needle: &Value) -> Vec { + let mut result = Vec::new(); + let mut sel = Selector::new(); + find_selector_recursive(haystack, needle, &mut result, &mut sel); + result +} + +fn find_selector_recursive( + haystack: &Value, + needle: &Value, + result: &mut Vec, + pos: &mut Selector, +) { + if needle == haystack { + result.push(pos.clone()); + // Haystack cant both contain needle and be needle + } else { + match haystack { + Value::Null => {} + Value::Bool(_) => {} + Value::Number(_) => {} + Value::String(_) => {} + Value::Array(arr) => { + for (idx, subhaystack) in arr.iter().enumerate() { + pos.push(SelectorPart::Index(idx)); + find_selector_recursive(subhaystack, needle, result, pos); + pos.pop().unwrap(); + } + } + Value::Object(obj) => { + for (key, subhaystack) in obj { + pos.push(SelectorPart::Field(key.clone())); + find_selector_recursive(subhaystack, needle, result, pos); + pos.pop().unwrap(); + } + } + } + } +} diff --git a/src/tools/jsondoclint/src/main.rs b/src/tools/jsondoclint/src/main.rs index 4df8fbc29a2b..1d02482421ba 100644 --- a/src/tools/jsondoclint/src/main.rs +++ b/src/tools/jsondoclint/src/main.rs @@ -3,8 +3,10 @@ use std::env; use anyhow::{anyhow, bail, Result}; use fs_err as fs; use rustdoc_json_types::{Crate, Id, FORMAT_VERSION}; +use serde_json::Value; pub(crate) mod item_kind; +mod json_find; mod validator; #[derive(Debug)] @@ -21,8 +23,10 @@ enum ErrorKind { fn main() -> Result<()> { let path = env::args().nth(1).ok_or_else(|| anyhow!("no path given"))?; - let contents = fs::read_to_string(path)?; + let contents = fs::read_to_string(&path)?; let krate: Crate = serde_json::from_str(&contents)?; + // TODO: Only load if nessessary. + let krate_json: Value = serde_json::from_str(&contents)?; assert_eq!(krate.format_version, FORMAT_VERSION); let mut validator = validator::Validator::new(&krate); @@ -31,11 +35,29 @@ fn main() -> Result<()> { if !validator.errs.is_empty() { for err in validator.errs { match err.kind { - ErrorKind::NotFound => eprintln!("{}: Not Found", err.id.0), + ErrorKind::NotFound => { + let sels = + json_find::find_selector(&krate_json, &Value::String(err.id.0.clone())); + match &sels[..] { + [] => unreachable!( + "id must be in crate, or it wouldn't be reported as not found" + ), + [sel] => eprintln!( + "{} not in index or paths, but refered to at '{}'", + err.id.0, + json_find::to_jsonpath(&sel) + ), + [sel, ..] => eprintln!( + "{} not in index or paths, but refered to at '{}' and more", + err.id.0, + json_find::to_jsonpath(&sel) + ), + } + } ErrorKind::Custom(msg) => eprintln!("{}: {}", err.id.0, msg), } } - bail!("Errors validating json"); + bail!("Errors validating json {path}"); } Ok(())