]> git.lizzy.rs Git - rust.git/blob - src/tools/tidy/src/alphabetical.rs
Auto merge of #102700 - oli-obk:0xDEAD_TAIT, r=compiler-errors
[rust.git] / src / tools / tidy / src / alphabetical.rs
1 //! Checks that a list of items is in alphabetical order
2 //!
3 //! To use, use the following annotation in the code:
4 //! ```rust
5 //! // tidy-alphabetical-start
6 //! fn aaa() {}
7 //! fn eee() {}
8 //! fn z() {}
9 //! // tidy-alphabetical-end
10 //! ```
11 //!
12 //! The following lines are ignored:
13 //! - Lines that are indented with more or less spaces than the first line
14 //! - Lines starting with `//`, `#[`, `)`, `]`, `}` if the comment has the same indentation as
15 //!   the first line
16 //!
17 //! If a line ends with an opening bracket, the line is ignored and the next line will have
18 //! its extra indentation ignored.
19
20 use std::{fmt::Display, path::Path};
21
22 use crate::walk::{filter_dirs, walk};
23
24 fn indentation(line: &str) -> usize {
25     line.find(|c| c != ' ').unwrap_or(0)
26 }
27
28 fn is_close_bracket(c: char) -> bool {
29     matches!(c, ')' | ']' | '}')
30 }
31
32 const START_COMMENT: &str = "// tidy-alphabetical-start";
33 const END_COMMENT: &str = "// tidy-alphabetical-end";
34
35 fn check_section<'a>(
36     file: impl Display,
37     lines: impl Iterator<Item = (usize, &'a str)>,
38     bad: &mut bool,
39 ) {
40     let content_lines = lines.take_while(|(_, line)| !line.contains(END_COMMENT));
41
42     let mut prev_line = String::new();
43     let mut first_indent = None;
44     let mut in_split_line = None;
45
46     for (line_idx, line) in content_lines {
47         if line.contains(START_COMMENT) {
48             tidy_error!(
49                 bad,
50                 "{file}:{} found `// tidy-alphabetical-start` expecting `// tidy-alphabetical-end`",
51                 line_idx
52             )
53         }
54
55         let indent = first_indent.unwrap_or_else(|| {
56             let indent = indentation(line);
57             first_indent = Some(indent);
58             indent
59         });
60
61         let line = if let Some(prev_split_line) = in_split_line {
62             in_split_line = None;
63             format!("{prev_split_line}{}", line.trim_start())
64         } else {
65             line.to_string()
66         };
67
68         if indentation(&line) != indent {
69             continue;
70         }
71
72         let trimmed_line = line.trim_start_matches(' ');
73
74         if trimmed_line.starts_with("//")
75             || trimmed_line.starts_with("#[")
76             || trimmed_line.starts_with(is_close_bracket)
77         {
78             continue;
79         }
80
81         if line.trim_end().ends_with('(') {
82             in_split_line = Some(line);
83             continue;
84         }
85
86         let prev_line_trimmed_lowercase = prev_line.trim_start_matches(' ').to_lowercase();
87
88         if trimmed_line.to_lowercase() < prev_line_trimmed_lowercase {
89             tidy_error!(bad, "{file}:{}: line not in alphabetical order", line_idx + 1,);
90         }
91
92         prev_line = line;
93     }
94 }
95
96 pub fn check(path: &Path, bad: &mut bool) {
97     walk(path, &mut filter_dirs, &mut |entry, contents| {
98         let file = &entry.path().display();
99
100         let mut lines = contents.lines().enumerate().peekable();
101         while let Some((_, line)) = lines.next() {
102             if line.contains(START_COMMENT) {
103                 check_section(file, &mut lines, bad);
104                 if lines.peek().is_none() {
105                     tidy_error!(
106                         bad,
107                         "{file}: reached end of file expecting `// tidy-alphabetical-end`"
108                     )
109                 }
110             }
111         }
112     });
113 }