Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Jsondocck improvements #82311

Merged
merged 6 commits into from
Feb 23, 2021
Merged
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 14 additions & 10 deletions src/test/rustdoc-json/nested.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,28 @@
// edition:2018

// @has nested.json "$.index[*][?(@.name=='nested')].kind" \"module\"
// @has - "$.index[*][?(@.name=='nested')].inner.is_crate" true
// @is nested.json "$.index[*][?(@.name=='nested')].kind" \"module\"
// @is - "$.index[*][?(@.name=='nested')].inner.is_crate" true
// @count - "$.index[*][?(@.name=='nested')].inner.items[*]" 1

// @has nested.json "$.index[*][?(@.name=='l1')].kind" \"module\"
// @has - "$.index[*][?(@.name=='l1')].inner.is_crate" false
// @is nested.json "$.index[*][?(@.name=='l1')].kind" \"module\"
// @is - "$.index[*][?(@.name=='l1')].inner.is_crate" false
// @count - "$.index[*][?(@.name=='l1')].inner.items[*]" 2
pub mod l1 {

// @has nested.json "$.index[*][?(@.name=='l3')].kind" \"module\"
// @has - "$.index[*][?(@.name=='l3')].inner.is_crate" false
// @is nested.json "$.index[*][?(@.name=='l3')].kind" \"module\"
// @is - "$.index[*][?(@.name=='l3')].inner.is_crate" false
// @count - "$.index[*][?(@.name=='l3')].inner.items[*]" 1
// @set l3_id = - "$.index[*][?(@.name=='l3')].id"
// @has - "$.index[*][?(@.name=='l1')].inner.items[*]" $l3_id
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$ already has a meaning in JSONPath, it means the root. I guess that shouldn't overlap here because you're testing a JSON value, it's not part of the path syntax? What happens if you want to match a string with $ in it?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

$foo -> Variable foo,
"$foo" -> String $foo

Because json can only start with ", {, or [, it is unambiguous

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

But this should be documented

pub mod l3 {

// @has nested.json "$.index[*][?(@.name=='L4')].kind" \"struct\"
// @has - "$.index[*][?(@.name=='L4')].inner.struct_type" \"unit\"
// @is nested.json "$.index[*][?(@.name=='L4')].kind" \"struct\"
// @is - "$.index[*][?(@.name=='L4')].inner.struct_type" \"unit\"
// @set l4_id = - "$.index[*][?(@.name=='L4')].id"
// @has - "$.index[*][?(@.name=='l3')].inner.items[*]" $l4_id
pub struct L4;
}
// @has nested.json "$.index[*][?(@.inner.span=='l3::L4')].kind" \"import\"
// @has - "$.index[*][?(@.inner.span=='l3::L4')].inner.glob" false
// @is nested.json "$.index[*][?(@.inner.span=='l3::L4')].kind" \"import\"
// @is - "$.index[*][?(@.inner.span=='l3::L4')].inner.glob" false
pub use l3::L4;
}
2 changes: 2 additions & 0 deletions src/tools/jsondocck/src/cache.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ pub struct Cache {
root: PathBuf,
files: HashMap<PathBuf, String>,
values: HashMap<PathBuf, Value>,
pub variables: HashMap<String, Value>,
last_path: Option<PathBuf>,
}

Expand All @@ -19,6 +20,7 @@ impl Cache {
root: Path::new(doc_dir).to_owned(),
files: HashMap::new(),
values: HashMap::new(),
variables: HashMap::new(),
last_path: None,
}
}
Expand Down
53 changes: 49 additions & 4 deletions src/tools/jsondocck/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ use jsonpath_lib::select;
use lazy_static::lazy_static;
use regex::{Regex, RegexBuilder};
use serde_json::Value;
use std::borrow::Cow;
use std::{env, fmt, fs};

mod cache;
Expand Down Expand Up @@ -48,13 +49,16 @@ pub struct Command {
pub enum CommandKind {
Has,
Count,
Is,
Set,
}

impl CommandKind {
fn validate(&self, args: &[String], command_num: usize, lineno: usize) -> bool {
let count = match self {
CommandKind::Has => (1..=3).contains(&args.len()),
CommandKind::Count => 3 == args.len(),
CommandKind::Count | CommandKind::Is => 3 == args.len(),
CommandKind::Set => 4 == args.len(),
};

if !count {
Expand Down Expand Up @@ -83,6 +87,8 @@ impl fmt::Display for CommandKind {
let text = match self {
CommandKind::Has => "has",
CommandKind::Count => "count",
CommandKind::Is => "is",
CommandKind::Set => "set",
};
write!(f, "{}", text)
}
Expand Down Expand Up @@ -127,6 +133,8 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
let cmd = match cmd {
"has" => CommandKind::Has,
"count" => CommandKind::Count,
"is" => CommandKind::Is,
"set" => CommandKind::Set,
_ => {
print_err(&format!("Unrecognized command name `@{}`", cmd), lineno);
errors = true;
Expand Down Expand Up @@ -180,6 +188,7 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
/// Performs the actual work of ensuring a command passes. Generally assumes the command
/// is syntactically valid.
fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
// FIXME: Be more granular about why, (e.g. syntax error, count not equal)
let result = match command.kind {
CommandKind::Has => {
match command.args.len() {
Expand All @@ -199,9 +208,8 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
let val = cache.get_value(&command.args[0])?;
match select(&val, &command.args[1]) {
Ok(results) => {
let pat: Value = serde_json::from_str(&command.args[2]).unwrap();

!results.is_empty() && results.into_iter().any(|val| *val == pat)
let pat = string_to_value(&command.args[2], cache);
results.contains(&pat.as_ref())
}
Err(_) => false,
}
Expand All @@ -220,6 +228,35 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
Err(_) => false,
}
}
CommandKind::Is => {
// @has <path> <jsonpath> <value> = check *exactly one* item matched by path, and it equals value
assert_eq!(command.args.len(), 3);
let val = cache.get_value(&command.args[0])?;
match select(&val, &command.args[1]) {
Ok(results) => {
let pat = string_to_value(&command.args[2], cache);
results.len() == 1 && results[0] == pat.as_ref()
}
Err(_) => false,
}
}
// FIXME, Figure out semantics for @!set
jyn514 marked this conversation as resolved.
Show resolved Hide resolved
CommandKind::Set => {
// @set <name> = <path> <jsonpath>
assert_eq!(command.args.len(), 4);
assert_eq!(command.args[1], "=", "Expected an `=`");
let val = cache.get_value(&command.args[2])?;

match select(&val, &command.args[3]) {
Ok(results) => {
assert_eq!(results.len(), 1);
let r = cache.variables.insert(command.args[0].clone(), results[0].clone());
assert!(r.is_none(), "Name collision: {} is duplicated", command.args[0]);
true
}
Err(_) => false,
}
}
};

if result == command.negated {
Expand Down Expand Up @@ -247,3 +284,11 @@ fn check_command(command: Command, cache: &mut Cache) -> Result<(), CkError> {
Ok(())
}
}

fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
if s.starts_with("$") {
Cow::Borrowed(&cache.variables[&s[1..]])
} else {
Cow::Owned(serde_json::from_str(s).unwrap())
}
}