tidy/
rustdoc_js.rs

1//! Tidy check to ensure that rustdoc templates didn't forget a `{# #}` to strip extra whitespace
2//! characters.
3
4use std::ffi::OsStr;
5use std::path::{Path, PathBuf};
6use std::process::Command;
7
8use ignore::DirEntry;
9
10use crate::walk::walk_no_read;
11
12fn run_eslint(args: &[PathBuf], config_folder: PathBuf, bad: &mut bool) {
13    let mut child = match Command::new("npx")
14        .arg("eslint")
15        .arg("-c")
16        .arg(config_folder.join(".eslintrc.js"))
17        .args(args)
18        .spawn()
19    {
20        Ok(child) => child,
21        Err(error) => {
22            *bad = true;
23            eprintln!("failed to run eslint: {error:?}");
24            return;
25        }
26    };
27    match child.wait() {
28        Ok(exit_status) => {
29            if exit_status.success() {
30                return;
31            }
32            eprintln!("eslint command failed");
33        }
34        Err(error) => eprintln!("eslint command failed: {error:?}"),
35    }
36    *bad = true;
37}
38
39fn get_eslint_version_inner(global: bool) -> Option<String> {
40    let mut command = Command::new("npm");
41    command.arg("list").arg("--parseable").arg("--long").arg("--depth=0");
42    if global {
43        command.arg("--global");
44    }
45    let output = command.output().ok()?;
46    let lines = String::from_utf8_lossy(&output.stdout);
47    lines.lines().find_map(|l| l.split(':').nth(1)?.strip_prefix("eslint@")).map(|v| v.to_owned())
48}
49
50fn get_eslint_version() -> Option<String> {
51    get_eslint_version_inner(false).or_else(|| get_eslint_version_inner(true))
52}
53
54pub fn check(librustdoc_path: &Path, tools_path: &Path, src_path: &Path, bad: &mut bool) {
55    let eslint_version_path =
56        src_path.join("ci/docker/host-x86_64/mingw-check-tidy/eslint.version");
57    let eslint_version = match std::fs::read_to_string(&eslint_version_path) {
58        Ok(version) => version.trim().to_string(),
59        Err(error) => {
60            *bad = true;
61            eprintln!("failed to read `{}`: {error:?}", eslint_version_path.display());
62            return;
63        }
64    };
65    match get_eslint_version() {
66        Some(version) => {
67            if version != eslint_version {
68                *bad = true;
69                eprintln!(
70                    "⚠️ Installed version of eslint (`{version}`) is different than the \
71                     one used in the CI (`{eslint_version}`)",
72                );
73                eprintln!(
74                    "You can install this version using `npm update eslint` or by using \
75                     `npm install eslint@{eslint_version}`",
76                );
77                return;
78            }
79        }
80        None => {
81            eprintln!("`eslint` doesn't seem to be installed. Skipping tidy check for JS files.");
82            eprintln!("You can install it using `npm install eslint@{eslint_version}`");
83            return;
84        }
85    }
86    let mut files_to_check = Vec::new();
87    walk_no_read(
88        &[&librustdoc_path.join("html/static/js")],
89        |path, is_dir| is_dir || !path.extension().is_some_and(|ext| ext == OsStr::new("js")),
90        &mut |path: &DirEntry| {
91            files_to_check.push(path.path().into());
92        },
93    );
94    println!("Running eslint on rustdoc JS files");
95    run_eslint(&files_to_check, librustdoc_path.join("html/static"), bad);
96
97    run_eslint(&[tools_path.join("rustdoc-js/tester.js")], tools_path.join("rustdoc-js"), bad);
98    run_eslint(&[tools_path.join("rustdoc-gui/tester.js")], tools_path.join("rustdoc-gui"), bad);
99}