summaryrefslogtreecommitdiffstats
path: root/src/tools/jsondoclint
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:19:13 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-17 12:19:13 +0000
commit218caa410aa38c29984be31a5229b9fa717560ee (patch)
treec54bd55eeb6e4c508940a30e94c0032fbd45d677 /src/tools/jsondoclint
parentReleasing progress-linux version 1.67.1+dfsg1-1~progress7.99u1. (diff)
downloadrustc-218caa410aa38c29984be31a5229b9fa717560ee.tar.xz
rustc-218caa410aa38c29984be31a5229b9fa717560ee.zip
Merging upstream version 1.68.2+dfsg1.
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'src/tools/jsondoclint')
-rw-r--r--src/tools/jsondoclint/Cargo.toml2
-rw-r--r--src/tools/jsondoclint/src/json_find.rs3
-rw-r--r--src/tools/jsondoclint/src/main.rs98
-rw-r--r--src/tools/jsondoclint/src/validator.rs76
-rw-r--r--src/tools/jsondoclint/src/validator/tests.rs120
5 files changed, 245 insertions, 54 deletions
diff --git a/src/tools/jsondoclint/Cargo.toml b/src/tools/jsondoclint/Cargo.toml
index 84a6c7f96..8990310a4 100644
--- a/src/tools/jsondoclint/Cargo.toml
+++ b/src/tools/jsondoclint/Cargo.toml
@@ -7,6 +7,8 @@ edition = "2021"
[dependencies]
anyhow = "1.0.62"
+clap = { version = "4.0.15", features = ["derive"] }
fs-err = "2.8.1"
rustdoc-json-types = { version = "0.1.0", path = "../../rustdoc-json-types" }
+serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85"
diff --git a/src/tools/jsondoclint/src/json_find.rs b/src/tools/jsondoclint/src/json_find.rs
index 70e7440f7..a183c4068 100644
--- a/src/tools/jsondoclint/src/json_find.rs
+++ b/src/tools/jsondoclint/src/json_find.rs
@@ -1,8 +1,9 @@
use std::fmt::Write;
+use serde::Serialize;
use serde_json::Value;
-#[derive(Debug, Clone, PartialEq, Eq)]
+#[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum SelectorPart {
Field(String),
Index(usize),
diff --git a/src/tools/jsondoclint/src/main.rs b/src/tools/jsondoclint/src/main.rs
index fc54c421b..05e938f4f 100644
--- a/src/tools/jsondoclint/src/main.rs
+++ b/src/tools/jsondoclint/src/main.rs
@@ -1,59 +1,103 @@
-use std::env;
+use std::io::{BufWriter, Write};
-use anyhow::{anyhow, bail, Result};
+use anyhow::{bail, Result};
+use clap::Parser;
use fs_err as fs;
use rustdoc_json_types::{Crate, Id, FORMAT_VERSION};
+use serde::Serialize;
use serde_json::Value;
pub(crate) mod item_kind;
mod json_find;
mod validator;
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
struct Error {
kind: ErrorKind,
id: Id,
}
-#[derive(Debug, PartialEq, Eq)]
+#[derive(Debug, PartialEq, Eq, Serialize, Clone)]
enum ErrorKind {
- NotFound,
+ NotFound(Vec<json_find::Selector>),
Custom(String),
}
+#[derive(Debug, Serialize)]
+struct JsonOutput {
+ path: String,
+ errors: Vec<Error>,
+}
+
+#[derive(Parser)]
+struct Cli {
+ /// The path to the json file to be linted
+ path: String,
+
+ /// Show verbose output
+ #[arg(long)]
+ verbose: bool,
+
+ #[arg(long)]
+ json_output: Option<String>,
+}
+
fn main() -> Result<()> {
- let path = env::args().nth(1).ok_or_else(|| anyhow!("no path given"))?;
+ let Cli { path, verbose, json_output } = Cli::parse();
+
let contents = fs::read_to_string(&path)?;
let krate: Crate = serde_json::from_str(&contents)?;
assert_eq!(krate.format_version, FORMAT_VERSION);
- let mut validator = validator::Validator::new(&krate);
+ let krate_json: Value = serde_json::from_str(&contents)?;
+
+ let mut validator = validator::Validator::new(&krate, krate_json);
validator.check_crate();
+ if let Some(json_output) = json_output {
+ let output = JsonOutput { path: path.clone(), errors: validator.errs.clone() };
+ let mut f = BufWriter::new(fs::File::create(json_output)?);
+ serde_json::to_writer(&mut f, &output)?;
+ f.flush()?;
+ }
+
if !validator.errs.is_empty() {
for err in validator.errs {
match err.kind {
- ErrorKind::NotFound => {
- let krate_json: Value = serde_json::from_str(&contents)?;
-
- 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::NotFound(sels) => match &sels[..] {
+ [] => {
+ unreachable!(
+ "id {:?} must be in crate, or it wouldn't be reported as not found",
+ err.id
+ )
+ }
+ [sel] => eprintln!(
+ "{} not in index or paths, but refered to at '{}'",
+ err.id.0,
+ json_find::to_jsonpath(&sel)
+ ),
+ [sel, ..] => {
+ if verbose {
+ let sels = sels
+ .iter()
+ .map(json_find::to_jsonpath)
+ .map(|i| format!("'{i}'"))
+ .collect::<Vec<_>>()
+ .join(", ");
+ eprintln!(
+ "{} not in index or paths, but refered to at {sels}",
+ err.id.0
+ );
+ } else {
+ eprintln!(
+ "{} not in index or paths, but refered to at '{}' and {} more",
+ err.id.0,
+ json_find::to_jsonpath(&sel),
+ sels.len() - 1,
+ )
+ }
}
- }
+ },
ErrorKind::Custom(msg) => eprintln!("{}: {}", err.id.0, msg),
}
}
diff --git a/src/tools/jsondoclint/src/validator.rs b/src/tools/jsondoclint/src/validator.rs
index e15f5fe3c..c6f55410e 100644
--- a/src/tools/jsondoclint/src/validator.rs
+++ b/src/tools/jsondoclint/src/validator.rs
@@ -3,12 +3,16 @@ use std::hash::Hash;
use rustdoc_json_types::{
Constant, Crate, DynTrait, Enum, FnDecl, Function, FunctionPointer, GenericArg, GenericArgs,
- GenericBound, GenericParamDef, Generics, Id, Impl, Import, ItemEnum, Module, OpaqueTy, Path,
- Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, Type, TypeBinding,
- TypeBindingKind, Typedef, Union, Variant, WherePredicate,
+ GenericBound, GenericParamDef, Generics, Id, Impl, Import, ItemEnum, ItemSummary, Module,
+ OpaqueTy, Path, Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias,
+ Type, TypeBinding, TypeBindingKind, Typedef, Union, Variant, VariantKind, WherePredicate,
};
+use serde_json::Value;
-use crate::{item_kind::Kind, Error, ErrorKind};
+use crate::{item_kind::Kind, json_find, Error, ErrorKind};
+
+// This is a rustc implementation detail that we rely on here
+const LOCAL_CRATE_ID: u32 = 0;
/// The Validator walks over the JSON tree, and ensures it is well formed.
/// It is made of several parts.
@@ -22,6 +26,7 @@ use crate::{item_kind::Kind, Error, ErrorKind};
pub struct Validator<'a> {
pub(crate) errs: Vec<Error>,
krate: &'a Crate,
+ krate_json: Value,
/// Worklist of Ids to check.
todo: HashSet<&'a Id>,
/// Ids that have already been visited, so don't need to be checked again.
@@ -39,9 +44,10 @@ enum PathKind {
}
impl<'a> Validator<'a> {
- pub fn new(krate: &'a Crate) -> Self {
+ pub fn new(krate: &'a Crate, krate_json: Value) -> Self {
Self {
krate,
+ krate_json,
errs: Vec::new(),
seen_ids: HashSet::new(),
todo: HashSet::new(),
@@ -50,12 +56,19 @@ impl<'a> Validator<'a> {
}
pub fn check_crate(&mut self) {
+ // Graph traverse the index
let root = &self.krate.root;
self.add_mod_id(root);
while let Some(id) = set_remove(&mut self.todo) {
self.seen_ids.insert(id);
self.check_item(id);
}
+
+ let root_crate_id = self.krate.index[root].crate_id;
+ assert_eq!(root_crate_id, LOCAL_CRATE_ID, "LOCAL_CRATE_ID is wrong");
+ for (id, item_info) in &self.krate.paths {
+ self.check_item_info(id, item_info);
+ }
}
fn check_item(&mut self, id: &'a Id) {
@@ -140,24 +153,24 @@ impl<'a> Validator<'a> {
}
fn check_variant(&mut self, x: &'a Variant, id: &'a Id) {
- match x {
- Variant::Plain(discr) => {
- if let Some(discr) = discr {
- if let (Err(_), Err(_)) =
- (discr.value.parse::<i128>(), discr.value.parse::<u128>())
- {
- self.fail(
- id,
- ErrorKind::Custom(format!(
- "Failed to parse discriminant value `{}`",
- discr.value
- )),
- );
- }
- }
+ let Variant { kind, discriminant } = x;
+
+ if let Some(discr) = discriminant {
+ if let (Err(_), Err(_)) = (discr.value.parse::<i128>(), discr.value.parse::<u128>()) {
+ self.fail(
+ id,
+ ErrorKind::Custom(format!(
+ "Failed to parse discriminant value `{}`",
+ discr.value
+ )),
+ );
}
- Variant::Tuple(tys) => tys.iter().flatten().for_each(|t| self.add_field_id(t)),
- Variant::Struct { fields, fields_stripped: _ } => {
+ }
+
+ match kind {
+ VariantKind::Plain => {}
+ VariantKind::Tuple(tys) => tys.iter().flatten().for_each(|t| self.add_field_id(t)),
+ VariantKind::Struct { fields, fields_stripped: _ } => {
fields.iter().for_each(|f| self.add_field_id(f))
}
}
@@ -361,6 +374,19 @@ impl<'a> Validator<'a> {
fp.generic_params.iter().for_each(|gpd| self.check_generic_param_def(gpd));
}
+ fn check_item_info(&mut self, id: &Id, item_info: &ItemSummary) {
+ // FIXME: Their should be a better way to determine if an item is local, rather than relying on `LOCAL_CRATE_ID`,
+ // which encodes rustc implementation details.
+ if item_info.crate_id == LOCAL_CRATE_ID && !self.krate.index.contains_key(id) {
+ self.errs.push(Error {
+ id: id.clone(),
+ kind: ErrorKind::Custom(
+ "Id for local item in `paths` but not in `index`".to_owned(),
+ ),
+ })
+ }
+ }
+
fn add_id_checked(&mut self, id: &'a Id, valid: fn(Kind) -> bool, expected: &str) {
if let Some(kind) = self.kind_of(id) {
if valid(kind) {
@@ -373,7 +399,11 @@ impl<'a> Validator<'a> {
} else {
if !self.missing_ids.contains(id) {
self.missing_ids.insert(id);
- self.fail(id, ErrorKind::NotFound)
+
+ let sels = json_find::find_selector(&self.krate_json, &Value::String(id.0.clone()));
+ assert_ne!(sels.len(), 0);
+
+ self.fail(id, ErrorKind::NotFound(sels))
}
}
}
diff --git a/src/tools/jsondoclint/src/validator/tests.rs b/src/tools/jsondoclint/src/validator/tests.rs
index c4aeee9c5..1ef41ff12 100644
--- a/src/tools/jsondoclint/src/validator/tests.rs
+++ b/src/tools/jsondoclint/src/validator/tests.rs
@@ -1,12 +1,17 @@
use std::collections::HashMap;
-use rustdoc_json_types::{Crate, Item, Visibility};
+use rustdoc_json_types::{Crate, Item, ItemKind, ItemSummary, Visibility, FORMAT_VERSION};
+
+use crate::json_find::SelectorPart;
use super::*;
#[track_caller]
fn check(krate: &Crate, errs: &[Error]) {
- let mut validator = Validator::new(krate);
+ let krate_string = serde_json::to_string(krate).unwrap();
+ let krate_json = serde_json::from_str(&krate_string).unwrap();
+
+ let mut validator = Validator::new(krate, krate_json);
validator.check_crate();
assert_eq!(errs, &validator.errs[..]);
@@ -46,5 +51,114 @@ fn errors_on_missing_links() {
format_version: rustdoc_json_types::FORMAT_VERSION,
};
- check(&k, &[Error { kind: ErrorKind::NotFound, id: id("1") }]);
+ check(
+ &k,
+ &[Error {
+ kind: ErrorKind::NotFound(vec![vec![
+ SelectorPart::Field("index".to_owned()),
+ SelectorPart::Field("0".to_owned()),
+ SelectorPart::Field("links".to_owned()),
+ SelectorPart::Field("Not Found".to_owned()),
+ ]]),
+ id: id("1"),
+ }],
+ );
+}
+
+// Test we would catch
+// https://github.com/rust-lang/rust/issues/104064#issuecomment-1368589718
+#[test]
+fn errors_on_local_in_paths_and_not_index() {
+ let krate = Crate {
+ root: id("0:0:1572"),
+ crate_version: None,
+ includes_private: false,
+ index: HashMap::from_iter([
+ (
+ id("0:0:1572"),
+ Item {
+ id: id("0:0:1572"),
+ crate_id: 0,
+ name: Some("microcore".to_owned()),
+ span: None,
+ visibility: Visibility::Public,
+ docs: None,
+ links: HashMap::from_iter([(("prim@i32".to_owned(), id("0:1:1571")))]),
+ attrs: Vec::new(),
+ deprecation: None,
+ inner: ItemEnum::Module(Module {
+ is_crate: true,
+ items: vec![id("0:1:717")],
+ is_stripped: false,
+ }),
+ },
+ ),
+ (
+ id("0:1:717"),
+ Item {
+ id: id("0:1:717"),
+ crate_id: 0,
+ name: Some("i32".to_owned()),
+ span: None,
+ visibility: Visibility::Public,
+ docs: None,
+ links: HashMap::default(),
+ attrs: Vec::new(),
+ deprecation: None,
+ inner: ItemEnum::Primitive(Primitive { name: "i32".to_owned(), impls: vec![] }),
+ },
+ ),
+ ]),
+ paths: HashMap::from_iter([(
+ id("0:1:1571"),
+ ItemSummary {
+ crate_id: 0,
+ path: vec!["microcore".to_owned(), "i32".to_owned()],
+ kind: ItemKind::Primitive,
+ },
+ )]),
+ external_crates: HashMap::default(),
+ format_version: rustdoc_json_types::FORMAT_VERSION,
+ };
+
+ check(
+ &krate,
+ &[Error {
+ id: id("0:1:1571"),
+ kind: ErrorKind::Custom("Id for local item in `paths` but not in `index`".to_owned()),
+ }],
+ );
+}
+
+#[test]
+#[should_panic = "LOCAL_CRATE_ID is wrong"]
+fn checks_local_crate_id_is_correct() {
+ let krate = Crate {
+ root: id("root"),
+ crate_version: None,
+ includes_private: false,
+ index: HashMap::from_iter([(
+ id("root"),
+ Item {
+ id: id("root"),
+ crate_id: LOCAL_CRATE_ID.wrapping_add(1),
+ name: Some("irrelavent".to_owned()),
+ span: None,
+ visibility: Visibility::Public,
+ docs: None,
+ links: HashMap::default(),
+ attrs: Vec::new(),
+ deprecation: None,
+ inner: ItemEnum::Module(Module {
+ is_crate: true,
+ items: vec![],
+ is_stripped: false,
+ }),
+ },
+ )]),
+ paths: HashMap::default(),
+ external_crates: HashMap::default(),
+ format_version: FORMAT_VERSION,
+ };
+ check(&krate, &[]);
}