about summary refs log tree commit diff
diff options
context:
space:
mode:
authorAlona Enraght-Moony <code@alona.page>2025-05-28 21:16:15 +0000
committerAlona Enraght-Moony <code@alona.page>2025-05-28 21:16:15 +0000
commit2c9fb22f040094b4cb982c84254bb5d0589c38e8 (patch)
tree1d48fb49379d05556d644f08a5733d95897a89c8
parent6f69710780d579b180ab38da4c1384d630f7bd31 (diff)
downloadrust-2c9fb22f040094b4cb982c84254bb5d0589c38e8.tar.gz
rust-2c9fb22f040094b4cb982c84254bb5d0589c38e8.zip
jsondoclint: Extract `Command`/`CommandKind` into its own file
-rw-r--r--src/tools/jsondocck/src/directive.rs230
-rw-r--r--src/tools/jsondocck/src/main.rs225
2 files changed, 233 insertions, 222 deletions
diff --git a/src/tools/jsondocck/src/directive.rs b/src/tools/jsondocck/src/directive.rs
new file mode 100644
index 00000000000..8617379ed27
--- /dev/null
+++ b/src/tools/jsondocck/src/directive.rs
@@ -0,0 +1,230 @@
+use std::borrow::Cow;
+use serde_json::Value;
+use crate::cache::Cache;
+
+#[derive(Debug)]
+pub struct Command {
+    pub kind: CommandKind,
+    pub path: String,
+    pub lineno: usize,
+}
+
+#[derive(Debug)]
+pub enum CommandKind {
+    /// `//@ has <path>`
+    ///
+    /// Checks the path exists.
+    HasPath,
+
+    /// `//@ has <path> <value>`
+    ///
+    /// Check one thing at the path  is equal to the value.
+    HasValue { value: String },
+
+    /// `//@ !has <path>`
+    ///
+    /// Checks the path doesn't exist.
+    HasNotPath,
+
+    /// `//@ !has <path> <value>`
+    ///
+    /// Checks the path exists, but doesn't have the given value.
+    HasNotValue { value: String },
+
+    /// `//@ is <path> <value>`
+    ///
+    /// Check the path is the given value.
+    Is { value: String },
+
+    /// `//@ is <path> <value> <value>...`
+    ///
+    /// Check that the path matches to exactly every given value.
+    IsMany { values: Vec<String> },
+
+    /// `//@ !is <path> <value>`
+    ///
+    /// Check the path isn't the given value.
+    IsNot { value: String },
+
+    /// `//@ count <path> <value>`
+    ///
+    /// Check the path has the expected number of matches.
+    CountIs { expected: usize },
+
+    /// `//@ set <name> = <path>`
+    Set { variable: String },
+}
+
+impl CommandKind {
+    /// Returns both the kind and the path.
+    ///
+    /// Returns `None` if the command isn't from jsondocck (e.g. from compiletest).
+    pub fn parse<'a>(
+        command_name: &str,
+        negated: bool,
+        args: &'a [String],
+    ) -> Option<(Self, &'a str)> {
+        let kind = match (command_name, negated) {
+            ("count", false) => {
+                assert_eq!(args.len(), 2);
+                let expected = args[1].parse().expect("invalid number for `count`");
+                Self::CountIs { expected }
+            }
+
+            ("ismany", false) => {
+                // FIXME: Make this >= 3, and migrate len(values)==1 cases to @is
+                assert!(args.len() >= 2, "Not enough args to `ismany`");
+                let values = args[1..].to_owned();
+                Self::IsMany { values }
+            }
+
+            ("is", false) => {
+                assert_eq!(args.len(), 2);
+                Self::Is { value: args[1].clone() }
+            }
+            ("is", true) => {
+                assert_eq!(args.len(), 2);
+                Self::IsNot { value: args[1].clone() }
+            }
+
+            ("set", false) => {
+                assert_eq!(args.len(), 3);
+                assert_eq!(args[1], "=");
+                return Some((Self::Set { variable: args[0].clone() }, &args[2]));
+            }
+
+            ("has", false) => match args {
+                [_path] => Self::HasPath,
+                [_path, value] => Self::HasValue { value: value.clone() },
+                _ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"),
+            },
+            ("has", true) => match args {
+                [_path] => Self::HasNotPath,
+                [_path, value] => Self::HasNotValue { value: value.clone() },
+                _ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"),
+            },
+
+            (_, false) if KNOWN_DIRECTIVE_NAMES.contains(&command_name) => {
+                return None;
+            }
+            _ => {
+                panic!("Invalid command `//@ {}{command_name}`", if negated { "!" } else { "" })
+            }
+        };
+
+        Some((kind, &args[0]))
+    }
+}
+
+impl Command {
+    /// Performs the actual work of ensuring a command passes.
+    pub fn check(&self, cache: &mut Cache) -> Result<(), String> {
+        let matches = cache.select(&self.path);
+        match &self.kind {
+            CommandKind::HasPath => {
+                if matches.is_empty() {
+                    return Err("matched to no values".to_owned());
+                }
+            }
+            CommandKind::HasNotPath => {
+                if !matches.is_empty() {
+                    return Err(format!("matched to {matches:?}, but wanted no matches"));
+                }
+            }
+            CommandKind::HasValue { value } => {
+                let want_value = string_to_value(value, cache);
+                if !matches.contains(&want_value.as_ref()) {
+                    return Err(format!(
+                        "matched to {matches:?}, which didn't contain {want_value:?}"
+                    ));
+                }
+            }
+            CommandKind::HasNotValue { value } => {
+                let wantnt_value = string_to_value(value, cache);
+                if matches.contains(&wantnt_value.as_ref()) {
+                    return Err(format!(
+                        "matched to {matches:?}, which contains unwanted {wantnt_value:?}"
+                    ));
+                } else if matches.is_empty() {
+                    return Err(format!(
+                        "got no matches, but expected some matched (not containing {wantnt_value:?}"
+                    ));
+                }
+            }
+
+            CommandKind::Is { value } => {
+                let want_value = string_to_value(value, cache);
+                let matched = get_one(&matches)?;
+                if matched != want_value.as_ref() {
+                    return Err(format!("matched to {matched:?} but want {want_value:?}"));
+                }
+            }
+            CommandKind::IsNot { value } => {
+                let wantnt_value = string_to_value(value, cache);
+                let matched = get_one(&matches)?;
+                if matched == wantnt_value.as_ref() {
+                    return Err(format!("got value {wantnt_value:?}, but want anything else"));
+                }
+            }
+
+            CommandKind::IsMany { values } => {
+                // Serde json doesn't implement Ord or Hash for Value, so we must
+                // use a Vec here. While in theory that makes setwize equality
+                // O(n^2), in practice n will never be large enough to matter.
+                let expected_values =
+                    values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>();
+                if expected_values.len() != matches.len() {
+                    return Err(format!(
+                        "Expected {} values, but matched to {} values ({:?})",
+                        expected_values.len(),
+                        matches.len(),
+                        matches
+                    ));
+                };
+                for got_value in matches {
+                    if !expected_values.iter().any(|exp| &**exp == got_value) {
+                        return Err(format!("has match {got_value:?}, which was not expected",));
+                    }
+                }
+            }
+            CommandKind::CountIs { expected } => {
+                if *expected != matches.len() {
+                    return Err(format!(
+                        "matched to `{matches:?}` with length {}, but expected length {expected}",
+                        matches.len(),
+                    ));
+                }
+            }
+            CommandKind::Set { variable } => {
+                let value = get_one(&matches)?;
+                let r = cache.variables.insert(variable.to_owned(), value.clone());
+                assert!(r.is_none(), "name collision: {variable:?} is duplicated");
+            }
+        }
+
+        Ok(())
+    }
+}
+
+fn get_one<'a>(matches: &[&'a Value]) -> Result<&'a Value, String> {
+    match matches {
+        [] => Err("matched to no values".to_owned()),
+        [matched] => Ok(matched),
+        _ => Err(format!("matched to multiple values {matches:?}, but want exactly 1")),
+    }
+}
+
+// FIXME: This setup is temporary until we figure out how to improve this situation.
+//        See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>.
+include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../compiletest/src/directive-list.rs"));
+
+fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
+    if s.starts_with("$") {
+        Cow::Borrowed(&cache.variables.get(&s[1..]).unwrap_or_else(|| {
+            // FIXME(adotinthevoid): Show line number
+            panic!("No variable: `{}`. Current state: `{:?}`", &s[1..], cache.variables)
+        }))
+    } else {
+        Cow::Owned(serde_json::from_str(s).expect(&format!("Cannot convert `{}` to json", s)))
+    }
+}
\ No newline at end of file
diff --git a/src/tools/jsondocck/src/main.rs b/src/tools/jsondocck/src/main.rs
index 65ad38da98b..80557b68243 100644
--- a/src/tools/jsondocck/src/main.rs
+++ b/src/tools/jsondocck/src/main.rs
@@ -1,17 +1,17 @@
-use std::borrow::Cow;
 use std::process::ExitCode;
 use std::sync::LazyLock;
 use std::{env, fs};
 
 use regex::{Regex, RegexBuilder};
-use serde_json::Value;
 
 mod cache;
 mod config;
+mod directive;
 mod error;
 
 use cache::Cache;
 use config::parse_config;
+use directive::{Command, CommandKind};
 use error::CkError;
 
 fn main() -> ExitCode {
@@ -25,7 +25,7 @@ fn main() -> ExitCode {
     };
 
     for command in commands {
-        if let Err(message) = check_command(&command, &mut cache) {
+        if let Err(message) = command.check(            &mut cache) {
             failed.push(CkError { command, message });
         }
     }
@@ -41,116 +41,6 @@ fn main() -> ExitCode {
     }
 }
 
-#[derive(Debug)]
-pub struct Command {
-    kind: CommandKind,
-    path: String,
-    lineno: usize,
-}
-
-#[derive(Debug)]
-enum CommandKind {
-    /// `//@ has <path>`
-    ///
-    /// Checks the path exists.
-    HasPath,
-
-    /// `//@ has <path> <value>`
-    ///
-    /// Check one thing at the path  is equal to the value.
-    HasValue { value: String },
-
-    /// `//@ !has <path>`
-    ///
-    /// Checks the path doesn't exist.
-    HasNotPath,
-
-    /// `//@ !has <path> <value>`
-    ///
-    /// Checks the path exists, but doesn't have the given value.
-    HasNotValue { value: String },
-
-    /// `//@ is <path> <value>`
-    ///
-    /// Check the path is the given value.
-    Is { value: String },
-
-    /// `//@ is <path> <value> <value>...`
-    ///
-    /// Check that the path matches to exactly every given value.
-    IsMany { values: Vec<String> },
-
-    /// `//@ !is <path> <value>`
-    ///
-    /// Check the path isn't the given value.
-    IsNot { value: String },
-
-    /// `//@ count <path> <value>`
-    ///
-    /// Check the path has the expected number of matches.
-    CountIs { expected: usize },
-
-    /// `//@ set <name> = <path>`
-    Set { variable: String },
-}
-
-impl CommandKind {
-    /// Returns both the kind and the path.
-    ///
-    /// Returns `None` if the command isn't from jsondocck (e.g. from compiletest).
-    fn parse<'a>(command_name: &str, negated: bool, args: &'a [String]) -> Option<(Self, &'a str)> {
-        let kind = match (command_name, negated) {
-            ("count", false) => {
-                assert_eq!(args.len(), 2);
-                let expected = args[1].parse().expect("invalid number for `count`");
-                Self::CountIs { expected }
-            }
-
-            ("ismany", false) => {
-                // FIXME: Make this >= 3, and migrate len(values)==1 cases to @is
-                assert!(args.len() >= 2, "Not enough args to `ismany`");
-                let values = args[1..].to_owned();
-                Self::IsMany { values }
-            }
-
-            ("is", false) => {
-                assert_eq!(args.len(), 2);
-                Self::Is { value: args[1].clone() }
-            }
-            ("is", true) => {
-                assert_eq!(args.len(), 2);
-                Self::IsNot { value: args[1].clone() }
-            }
-
-            ("set", false) => {
-                assert_eq!(args.len(), 3);
-                assert_eq!(args[1], "=");
-                return Some((Self::Set { variable: args[0].clone() }, &args[2]));
-            }
-
-            ("has", false) => match args {
-                [_path] => Self::HasPath,
-                [_path, value] => Self::HasValue { value: value.clone() },
-                _ => panic!("`//@ has` must have 2 or 3 arguments, but got {args:?}"),
-            },
-            ("has", true) => match args {
-                [_path] => Self::HasNotPath,
-                [_path, value] => Self::HasNotValue { value: value.clone() },
-                _ => panic!("`//@ !has` must have 2 or 3 arguments, but got {args:?}"),
-            },
-
-            (_, false) if KNOWN_DIRECTIVE_NAMES.contains(&command_name) => {
-                return None;
-            }
-            _ => {
-                panic!("Invalid command `//@ {}{command_name}`", if negated { "!" } else { "" })
-            }
-        };
-
-        Some((kind, &args[0]))
-    }
-}
-
 static LINE_PATTERN: LazyLock<Regex> = LazyLock::new(|| {
     RegexBuilder::new(
         r#"
@@ -183,10 +73,6 @@ fn print_err(msg: &str, lineno: usize) {
     eprintln!("Invalid command: {} on line {}", msg, lineno)
 }
 
-// FIXME: This setup is temporary until we figure out how to improve this situation.
-//        See <https://github.com/rust-lang/rust/issues/125813#issuecomment-2141953780>.
-include!(concat!(env!("CARGO_MANIFEST_DIR"), "/../compiletest/src/directive-list.rs"));
-
 /// Get a list of commands from a file.
 fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
     let mut commands = Vec::new();
@@ -222,108 +108,3 @@ fn get_commands(template: &str) -> Result<Vec<Command>, ()> {
 
     if !errors { Ok(commands) } else { Err(()) }
 }
-
-/// Performs the actual work of ensuring a command passes.
-fn check_command(command: &Command, cache: &mut Cache) -> Result<(), String> {
-    let matches = cache.select(&command.path);
-    match &command.kind {
-        CommandKind::HasPath => {
-            if matches.is_empty() {
-                return Err("matched to no values".to_owned());
-            }
-        }
-        CommandKind::HasNotPath => {
-            if !matches.is_empty() {
-                return Err(format!("matched to {matches:?}, but wanted no matches"));
-            }
-        }
-        CommandKind::HasValue { value } => {
-            let want_value = string_to_value(value, cache);
-            if !matches.contains(&want_value.as_ref()) {
-                return Err(format!("matched to {matches:?}, which didn't contain {want_value:?}"));
-            }
-        }
-        CommandKind::HasNotValue { value } => {
-            let wantnt_value = string_to_value(value, cache);
-            if matches.contains(&wantnt_value.as_ref()) {
-                return Err(format!(
-                    "matched to {matches:?}, which contains unwanted {wantnt_value:?}"
-                ));
-            } else if matches.is_empty() {
-                return Err(format!(
-                    "got no matches, but expected some matched (not containing {wantnt_value:?}"
-                ));
-            }
-        }
-
-        CommandKind::Is { value } => {
-            let want_value = string_to_value(value, cache);
-            let matched = get_one(&matches)?;
-            if matched != want_value.as_ref() {
-                return Err(format!("matched to {matched:?} but want {want_value:?}"));
-            }
-        }
-        CommandKind::IsNot { value } => {
-            let wantnt_value = string_to_value(value, cache);
-            let matched = get_one(&matches)?;
-            if matched == wantnt_value.as_ref() {
-                return Err(format!("got value {wantnt_value:?}, but want anything else"));
-            }
-        }
-
-        CommandKind::IsMany { values } => {
-            // Serde json doesn't implement Ord or Hash for Value, so we must
-            // use a Vec here. While in theory that makes setwize equality
-            // O(n^2), in practice n will never be large enough to matter.
-            let expected_values =
-                values.iter().map(|v| string_to_value(v, cache)).collect::<Vec<_>>();
-            if expected_values.len() != matches.len() {
-                return Err(format!(
-                    "Expected {} values, but matched to {} values ({:?})",
-                    expected_values.len(),
-                    matches.len(),
-                    matches
-                ));
-            };
-            for got_value in matches {
-                if !expected_values.iter().any(|exp| &**exp == got_value) {
-                    return Err(format!("has match {got_value:?}, which was not expected",));
-                }
-            }
-        }
-        CommandKind::CountIs { expected } => {
-            if *expected != matches.len() {
-                return Err(format!(
-                    "matched to `{matches:?}` with length {}, but expected length {expected}",
-                    matches.len(),
-                ));
-            }
-        }
-        CommandKind::Set { variable } => {
-            let value = get_one(&matches)?;
-            let r = cache.variables.insert(variable.to_owned(), value.clone());
-            assert!(r.is_none(), "name collision: {variable:?} is duplicated");
-        }
-    }
-
-    Ok(())
-}
-
-fn get_one<'a>(matches: &[&'a Value]) -> Result<&'a Value, String> {
-    match matches {
-        [] => Err("matched to no values".to_owned()),
-        [matched] => Ok(matched),
-        _ => Err(format!("matched to multiple values {matches:?}, but want exactly 1")),
-    }
-}
-
-fn string_to_value<'a>(s: &str, cache: &'a Cache) -> Cow<'a, Value> {
-    if s.starts_with("$") {
-        Cow::Borrowed(&cache.variables.get(&s[1..]).unwrap_or_else(|| {
-            // FIXME(adotinthevoid): Show line number
-            panic!("No variable: `{}`. Current state: `{:?}`", &s[1..], cache.variables)
-        }))
-    } else {
-        Cow::Owned(serde_json::from_str(s).expect(&format!("Cannot convert `{}` to json", s)))
-    }
-}