1
Fork 0

Rollup merge of #106381 - aDotInTheVoid:jsondoclint-more-options, r=notriddle

Jsondoclint: Add `--verbose` and `--json-output` options

There quite helpful for manually using jsondoclint as a debugging tool, especially on large files (these were written to look into core.json).

r? rustdoc
This commit is contained in:
Michael Goulet 2023-01-03 17:19:27 -08:00 committed by GitHub
commit f24dab764f
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 121 additions and 34 deletions

View file

@ -597,7 +597,7 @@ checksum = "23b71c3ce99b7611011217b366d923f1d0a7e07a92bb2dbf1e84508c673ca3bd"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
"clap_derive", "clap_derive 3.2.18",
"clap_lex 0.2.2", "clap_lex 0.2.2",
"indexmap", "indexmap",
"once_cell", "once_cell",
@ -614,7 +614,9 @@ checksum = "6bf8832993da70a4c6d13c581f4463c2bdda27b9bf1c5498dc4365543abe6d6f"
dependencies = [ dependencies = [
"atty", "atty",
"bitflags", "bitflags",
"clap_derive 4.0.13",
"clap_lex 0.3.0", "clap_lex 0.3.0",
"once_cell",
"strsim", "strsim",
"termcolor", "termcolor",
] ]
@ -641,6 +643,19 @@ dependencies = [
"syn", "syn",
] ]
[[package]]
name = "clap_derive"
version = "4.0.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c42f169caba89a7d512b5418b09864543eeb4d497416c917d7137863bd2076ad"
dependencies = [
"heck",
"proc-macro-error",
"proc-macro2",
"quote",
"syn",
]
[[package]] [[package]]
name = "clap_lex" name = "clap_lex"
version = "0.2.2" version = "0.2.2"
@ -2097,8 +2112,10 @@ name = "jsondoclint"
version = "0.1.0" version = "0.1.0"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"clap 4.0.15",
"fs-err", "fs-err",
"rustdoc-json-types", "rustdoc-json-types",
"serde",
"serde_json", "serde_json",
] ]

View file

@ -7,6 +7,8 @@ edition = "2021"
[dependencies] [dependencies]
anyhow = "1.0.62" anyhow = "1.0.62"
clap = { version = "4.0.15", features = ["derive"] }
fs-err = "2.8.1" fs-err = "2.8.1"
rustdoc-json-types = { version = "0.1.0", path = "../../rustdoc-json-types" } rustdoc-json-types = { version = "0.1.0", path = "../../rustdoc-json-types" }
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.85" serde_json = "1.0.85"

View file

@ -1,8 +1,9 @@
use std::fmt::Write; use std::fmt::Write;
use serde::Serialize;
use serde_json::Value; use serde_json::Value;
#[derive(Debug, Clone, PartialEq, Eq)] #[derive(Debug, Clone, PartialEq, Eq, Serialize)]
pub enum SelectorPart { pub enum SelectorPart {
Field(String), Field(String),
Index(usize), Index(usize),

View file

@ -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 fs_err as fs;
use rustdoc_json_types::{Crate, Id, FORMAT_VERSION}; use rustdoc_json_types::{Crate, Id, FORMAT_VERSION};
use serde::Serialize;
use serde_json::Value; use serde_json::Value;
pub(crate) mod item_kind; pub(crate) mod item_kind;
mod json_find; mod json_find;
mod validator; mod validator;
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Serialize, Clone)]
struct Error { struct Error {
kind: ErrorKind, kind: ErrorKind,
id: Id, id: Id,
} }
#[derive(Debug, PartialEq, Eq)] #[derive(Debug, PartialEq, Eq, Serialize, Clone)]
enum ErrorKind { enum ErrorKind {
NotFound, NotFound(Vec<json_find::Selector>),
Custom(String), 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<()> { 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 contents = fs::read_to_string(&path)?;
let krate: Crate = serde_json::from_str(&contents)?; let krate: Crate = serde_json::from_str(&contents)?;
assert_eq!(krate.format_version, FORMAT_VERSION); 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(); 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() { if !validator.errs.is_empty() {
for err in validator.errs { for err in validator.errs {
match err.kind { match err.kind {
ErrorKind::NotFound => { ErrorKind::NotFound(sels) => match &sels[..] {
let krate_json: Value = serde_json::from_str(&contents)?; [] => {
unreachable!(
let sels = "id {:?} must be in crate, or it wouldn't be reported as not found",
json_find::find_selector(&krate_json, &Value::String(err.id.0.clone())); err.id
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)
),
} }
} [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), ErrorKind::Custom(msg) => eprintln!("{}: {}", err.id.0, msg),
} }
} }

View file

@ -7,8 +7,9 @@ use rustdoc_json_types::{
Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, Type, TypeBinding, Primitive, ProcMacro, Static, Struct, StructKind, Term, Trait, TraitAlias, Type, TypeBinding,
TypeBindingKind, Typedef, Union, Variant, VariantKind, WherePredicate, 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};
/// The Validator walks over the JSON tree, and ensures it is well formed. /// The Validator walks over the JSON tree, and ensures it is well formed.
/// It is made of several parts. /// It is made of several parts.
@ -22,6 +23,7 @@ use crate::{item_kind::Kind, Error, ErrorKind};
pub struct Validator<'a> { pub struct Validator<'a> {
pub(crate) errs: Vec<Error>, pub(crate) errs: Vec<Error>,
krate: &'a Crate, krate: &'a Crate,
krate_json: Value,
/// Worklist of Ids to check. /// Worklist of Ids to check.
todo: HashSet<&'a Id>, todo: HashSet<&'a Id>,
/// Ids that have already been visited, so don't need to be checked again. /// Ids that have already been visited, so don't need to be checked again.
@ -39,9 +41,10 @@ enum PathKind {
} }
impl<'a> Validator<'a> { impl<'a> Validator<'a> {
pub fn new(krate: &'a Crate) -> Self { pub fn new(krate: &'a Crate, krate_json: Value) -> Self {
Self { Self {
krate, krate,
krate_json,
errs: Vec::new(), errs: Vec::new(),
seen_ids: HashSet::new(), seen_ids: HashSet::new(),
todo: HashSet::new(), todo: HashSet::new(),
@ -373,7 +376,11 @@ impl<'a> Validator<'a> {
} else { } else {
if !self.missing_ids.contains(id) { if !self.missing_ids.contains(id) {
self.missing_ids.insert(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))
} }
} }
} }

View file

@ -2,11 +2,16 @@ use std::collections::HashMap;
use rustdoc_json_types::{Crate, Item, Visibility}; use rustdoc_json_types::{Crate, Item, Visibility};
use crate::json_find::SelectorPart;
use super::*; use super::*;
#[track_caller] #[track_caller]
fn check(krate: &Crate, errs: &[Error]) { 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(); validator.check_crate();
assert_eq!(errs, &validator.errs[..]); assert_eq!(errs, &validator.errs[..]);
@ -46,5 +51,16 @@ fn errors_on_missing_links() {
format_version: rustdoc_json_types::FORMAT_VERSION, 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"),
}],
);
} }