tidy/
rustdoc_templates.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;
6
7use ignore::DirEntry;
8
9use crate::diagnostics::{CheckId, DiagCtx};
10use crate::walk::walk;
11
12// Array containing `("beginning of tag", "end of tag")`.
13const TAGS: &[(&str, &str)] = &[("{#", "#}"), ("{%", "%}"), ("{{", "}}")];
14
15pub fn check(librustdoc_path: &Path, diag_ctx: DiagCtx) {
16    let mut check = diag_ctx.start_check(CheckId::new("rustdoc_templates").path(librustdoc_path));
17
18    walk(
19        &librustdoc_path.join("html/templates"),
20        |path, is_dir| is_dir || path.extension().is_none_or(|ext| ext != OsStr::new("html")),
21        &mut |path: &DirEntry, file_content: &str| {
22            let mut lines = file_content.lines().enumerate().peekable();
23
24            while let Some((pos, line)) = lines.next() {
25                let line = line.trim();
26                if let Some(need_next_line_check) = TAGS.iter().find_map(|(tag, end_tag)| {
27                    // We first check if the line ends with a jinja tag.
28                    if !line.ends_with(end_tag) {
29                        None
30                    // Then we check if this a comment tag.
31                    } else if *tag != "{#" {
32                        Some(false)
33                    // And finally we check if the comment is empty (ie, only there to strip
34                    // extra whitespace characters).
35                    } else if let Some(start_pos) = line.rfind(tag) {
36                        Some(line[start_pos + 2..].trim() == "#}")
37                    } else {
38                        Some(false)
39                    }
40                }) {
41                    // All good, the line is ending is a jinja tag. But maybe this tag is useless
42                    // if the next line starts with a jinja tag as well!
43                    //
44                    // However, only (empty) comment jinja tags are concerned about it.
45                    if need_next_line_check
46                        && lines.peek().is_some_and(|(_, next_line)| {
47                            let next_line = next_line.trim_start();
48                            TAGS.iter().any(|(tag, _)| next_line.starts_with(tag))
49                        })
50                    {
51                        // It seems like ending this line with a jinja tag is not needed after all.
52                        check.error(format!(
53                            "`{}` at line {}: unneeded `{{# #}}` tag at the end of the line",
54                            path.path().display(),
55                            pos + 1,
56                        ));
57                    }
58                    continue;
59                }
60                let Some(next_line) = lines.peek().map(|(_, next_line)| next_line.trim()) else {
61                    continue;
62                };
63                if TAGS.iter().any(|(tag, _)| next_line.starts_with(tag)) {
64                    continue;
65                }
66                // Maybe this is a multi-line tag, let's filter it out then.
67                match TAGS.iter().find_map(|(tag, end_tag)| {
68                    if line.rfind(tag).is_some() { Some(end_tag) } else { None }
69                }) {
70                    None => {
71                        // No it's not, let's error.
72                        check.error(format!(
73                            "`{}` at line {}: missing `{{# #}}` at the end of the line",
74                            path.path().display(),
75                            pos + 1,
76                        ));
77                    }
78                    Some(end_tag) => {
79                        // We skip the tag.
80                        while let Some((_, next_line)) = lines.peek() {
81                            if next_line.contains(end_tag) {
82                                break;
83                            }
84                            lines.next();
85                        }
86                    }
87                }
88            }
89        },
90    );
91}