tidy/
rustdoc_json.rs

1//! Tidy check to ensure that `FORMAT_VERSION` was correctly updated if `rustdoc-json-types` was
2//! updated as well.
3
4use std::ffi::OsStr;
5use std::path::Path;
6use std::process::Command;
7use std::str::FromStr;
8
9use build_helper::ci::CiEnv;
10use build_helper::git::{GitConfig, get_closest_upstream_commit};
11use build_helper::stage0_parser::parse_stage0_file;
12
13const RUSTDOC_JSON_TYPES: &str = "src/rustdoc-json-types";
14
15fn git_diff<S: AsRef<OsStr>>(base_commit: &str, extra_arg: S) -> Option<String> {
16    let output = Command::new("git").arg("diff").arg(base_commit).arg(extra_arg).output().ok()?;
17    Some(String::from_utf8_lossy(&output.stdout).into())
18}
19
20fn error_if_in_ci(ci_env: CiEnv, msg: &str, bad: &mut bool) {
21    if ci_env.is_running_in_ci() {
22        *bad = true;
23        eprintln!("error in `rustdoc_json` tidy check: {msg}");
24    } else {
25        eprintln!("{msg}. Skipping `rustdoc_json` tidy check");
26    }
27}
28
29pub fn check(src_path: &Path, bad: &mut bool) {
30    println!("Checking tidy rustdoc_json...");
31    let stage0 = parse_stage0_file();
32    let ci_env = CiEnv::current();
33    let base_commit = match get_closest_upstream_commit(
34        None,
35        &GitConfig {
36            nightly_branch: &stage0.config.nightly_branch,
37            git_merge_commit_email: &stage0.config.git_merge_commit_email,
38        },
39        ci_env,
40    ) {
41        Ok(Some(commit)) => commit,
42        Ok(None) => {
43            error_if_in_ci(ci_env, "no base commit found", bad);
44            return;
45        }
46        Err(error) => {
47            error_if_in_ci(ci_env, &format!("failed to retrieve base commit: {error}"), bad);
48            return;
49        }
50    };
51
52    // First we check that `src/rustdoc-json-types` was modified.
53    match git_diff(&base_commit, "--name-status") {
54        Some(output) => {
55            if !output
56                .lines()
57                .any(|line| line.starts_with("M") && line.contains(RUSTDOC_JSON_TYPES))
58            {
59                // `rustdoc-json-types` was not modified so nothing more to check here.
60                println!("`rustdoc-json-types` was not modified.");
61                return;
62            }
63        }
64        None => {
65            *bad = true;
66            eprintln!("error: failed to run `git diff` in rustdoc_json check");
67            return;
68        }
69    }
70    // Then we check that if `FORMAT_VERSION` was updated, the `Latest feature:` was also updated.
71    match git_diff(&base_commit, src_path.join("rustdoc-json-types")) {
72        Some(output) => {
73            let mut format_version_updated = false;
74            let mut latest_feature_comment_updated = false;
75            let mut new_version = None;
76            let mut old_version = None;
77            for line in output.lines() {
78                if line.starts_with("+pub const FORMAT_VERSION: u32 =") {
79                    format_version_updated = true;
80                    new_version = line
81                        .split('=')
82                        .nth(1)
83                        .and_then(|s| s.trim().split(';').next())
84                        .and_then(|s| u32::from_str(s.trim()).ok());
85                } else if line.starts_with("-pub const FORMAT_VERSION: u32 =") {
86                    old_version = line
87                        .split('=')
88                        .nth(1)
89                        .and_then(|s| s.trim().split(';').next())
90                        .and_then(|s| u32::from_str(s.trim()).ok());
91                } else if line.starts_with("+// Latest feature:") {
92                    latest_feature_comment_updated = true;
93                }
94            }
95            if format_version_updated != latest_feature_comment_updated {
96                *bad = true;
97                if latest_feature_comment_updated {
98                    eprintln!(
99                        "error in `rustdoc_json` tidy check: `Latest feature` comment was updated \
100                         whereas `FORMAT_VERSION` wasn't in `{RUSTDOC_JSON_TYPES}/lib.rs`"
101                    );
102                } else {
103                    eprintln!(
104                        "error in `rustdoc_json` tidy check: `Latest feature` comment was not \
105                         updated whereas `FORMAT_VERSION` was in `{RUSTDOC_JSON_TYPES}/lib.rs`"
106                    );
107                }
108            }
109            match (new_version, old_version) {
110                (Some(new_version), Some(old_version)) if new_version != old_version + 1 => {
111                    *bad = true;
112                    eprintln!(
113                        "error in `rustdoc_json` tidy check: invalid `FORMAT_VERSION` increase in \
114                         `{RUSTDOC_JSON_TYPES}/lib.rs`, should be `{}`, found `{new_version}`",
115                        old_version + 1,
116                    );
117                }
118                _ => {}
119            }
120        }
121        None => {
122            *bad = true;
123            eprintln!("error: failed to run `git diff` in rustdoc_json check");
124        }
125    }
126}