about summary refs log tree commit diff
diff options
context:
space:
mode:
authorNixon Enraght-Moony <nixon.emoony@gmail.com>2022-08-24 23:18:53 +0100
committerNixon Enraght-Moony <nixon.emoony@gmail.com>2022-09-14 16:14:15 +0100
commit41d35a97f9f33e265de53b8f04abe307d7616641 (patch)
treef88771e6edd7f31bce683aac4ee9f34edb3924fc
parent5f1bc6fc5e362c5955f6a06a7cf21e62b97c86c7 (diff)
downloadrust-41d35a97f9f33e265de53b8f04abe307d7616641.tar.gz
rust-41d35a97f9f33e265de53b8f04abe307d7616641.zip
jsondocck: Find path to Id's not in index
-rw-r--r--src/tools/jsondoclint/src/json_find.rs74
-rw-r--r--src/tools/jsondoclint/src/main.rs28
2 files changed, 99 insertions, 3 deletions
diff --git a/src/tools/jsondoclint/src/json_find.rs b/src/tools/jsondoclint/src/json_find.rs
new file mode 100644
index 00000000000..95ea8866609
--- /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<SelectorPart>;
+
+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<Selector> {
+    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<Selector>,
+    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 4df8fbc29a2..1d02482421b 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(())