tidy/
rustdoc_css_themes.rs

1//! Tidy check to make sure light and dark themes are synchronized between
2//! JS-controlled rustdoc.css and noscript.css
3
4use std::path::Path;
5
6use crate::diagnostics::{CheckId, DiagCtx, RunningCheck};
7
8pub fn check(librustdoc_path: &Path, diag_ctx: DiagCtx) {
9    let mut check = diag_ctx.start_check(CheckId::new("rustdoc_css_themes").path(librustdoc_path));
10
11    let rustdoc_css = "html/static/css/rustdoc.css";
12    let noscript_css = "html/static/css/noscript.css";
13    let rustdoc_css_contents = std::fs::read_to_string(librustdoc_path.join(rustdoc_css))
14        .unwrap_or_else(|e| panic!("failed to read librustdoc/{rustdoc_css}: {e}"));
15    let noscript_css_contents = std::fs::read_to_string(librustdoc_path.join(noscript_css))
16        .unwrap_or_else(|e| panic!("failed to read librustdoc/{noscript_css}: {e}"));
17    compare_themes_from_files(
18        "light",
19        rustdoc_css_contents.lines().enumerate().map(|(i, l)| (i + 1, l.trim())),
20        noscript_css_contents.lines().enumerate().map(|(i, l)| (i + 1, l.trim())),
21        &mut check,
22    );
23    compare_themes_from_files(
24        "dark",
25        rustdoc_css_contents.lines().enumerate(),
26        noscript_css_contents.lines().enumerate(),
27        &mut check,
28    );
29}
30
31fn compare_themes_from_files<'a>(
32    name: &str,
33    mut rustdoc_css_lines: impl Iterator<Item = (usize, &'a str)>,
34    mut noscript_css_lines: impl Iterator<Item = (usize, &'a str)>,
35    check: &mut RunningCheck,
36) {
37    let begin_theme_pat = format!("/* Begin theme: {name}");
38    let mut found_theme = None;
39    let mut found_theme_noscript = None;
40    while let Some((rustdoc_css_line_number, rustdoc_css_line)) = rustdoc_css_lines.next() {
41        if !rustdoc_css_line.starts_with(&begin_theme_pat) {
42            continue;
43        }
44        if let Some(found_theme) = found_theme {
45            check.error(format!(
46                "rustdoc.css contains two {name} themes on lines {rustdoc_css_line_number} and {found_theme}",
47            ));
48            return;
49        }
50        found_theme = Some(rustdoc_css_line_number);
51        while let Some((noscript_css_line_number, noscript_css_line)) = noscript_css_lines.next() {
52            if !noscript_css_line.starts_with(&begin_theme_pat) {
53                continue;
54            }
55            if let Some(found_theme_noscript) = found_theme_noscript {
56                check.error(format!(
57                    "noscript.css contains two {name} themes on lines {noscript_css_line_number} and {found_theme_noscript}",
58                ));
59                return;
60            }
61            found_theme_noscript = Some(noscript_css_line_number);
62            compare_themes(name, &mut rustdoc_css_lines, &mut noscript_css_lines, check);
63        }
64    }
65}
66
67fn compare_themes<'a>(
68    name: &str,
69    rustdoc_css_lines: impl Iterator<Item = (usize, &'a str)>,
70    noscript_css_lines: impl Iterator<Item = (usize, &'a str)>,
71    check: &mut RunningCheck,
72) {
73    let end_theme_pat = format!("/* End theme: {name}");
74    for (
75        (rustdoc_css_line_number, rustdoc_css_line),
76        (noscript_css_line_number, noscript_css_line),
77    ) in rustdoc_css_lines.zip(noscript_css_lines)
78    {
79        if noscript_css_line.starts_with(":root, :root:not([data-theme]) {")
80            && (rustdoc_css_line.starts_with(&format!(r#":root[data-theme="{name}"] {{"#))
81                || rustdoc_css_line.starts_with(&format!(
82                    r#":root[data-theme="{name}"], :root:not([data-theme]) {{"#
83                )))
84        {
85            // selectors are different between rustdoc.css and noscript.css
86            // that's why they both exist: one uses JS, the other uses media queries
87            continue;
88        }
89        if noscript_css_line.starts_with(&end_theme_pat)
90            && rustdoc_css_line.starts_with(&end_theme_pat)
91        {
92            break;
93        }
94        if rustdoc_css_line != noscript_css_line {
95            check.error(format!(
96                r#"noscript.css:{noscript_css_line_number} and rustdoc.css:{rustdoc_css_line_number} contain copies of {name} theme that are not the same
97- {noscript_css_line}
98+ {rustdoc_css_line}"#,
99            ));
100            return;
101        }
102    }
103}