1 //! Checks that a list of items is in alphabetical order
3 //! To use, use the following annotation in the code:
5 //! // tidy-alphabetical-start
9 //! // tidy-alphabetical-end
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
17 //! If a line ends with an opening bracket, the line is ignored and the next line will have
18 //! its extra indentation ignored.
20 use std::{fmt::Display, path::Path};
22 use crate::walk::{filter_dirs, walk};
24 fn indentation(line: &str) -> usize {
25 line.find(|c| c != ' ').unwrap_or(0)
28 fn is_close_bracket(c: char) -> bool {
29 matches!(c, ')' | ']' | '}')
32 // Don't let tidy check this here :D
33 const START_COMMENT: &str = concat!("// tidy-alphabetical", "-start");
34 const END_COMMENT: &str = "// tidy-alphabetical-end";
38 lines: impl Iterator<Item = (usize, &'a str)>,
41 let content_lines = lines.take_while(|(_, line)| !line.contains(END_COMMENT));
43 let mut prev_line = String::new();
44 let mut first_indent = None;
45 let mut in_split_line = None;
47 for (line_idx, line) in content_lines {
48 if line.contains(START_COMMENT) {
51 "{file}:{} found `{START_COMMENT}` expecting `{END_COMMENT}`",
56 let indent = first_indent.unwrap_or_else(|| {
57 let indent = indentation(line);
58 first_indent = Some(indent);
62 let line = if let Some(prev_split_line) = in_split_line {
64 format!("{prev_split_line}{}", line.trim_start())
69 if indentation(&line) != indent {
73 let trimmed_line = line.trim_start_matches(' ');
75 if trimmed_line.starts_with("//")
76 || trimmed_line.starts_with("#[")
77 || trimmed_line.starts_with(is_close_bracket)
82 if line.trim_end().ends_with('(') {
83 in_split_line = Some(line);
87 let prev_line_trimmed_lowercase = prev_line.trim_start_matches(' ').to_lowercase();
89 if trimmed_line.to_lowercase() < prev_line_trimmed_lowercase {
90 tidy_error!(bad, "{file}:{}: line not in alphabetical order", line_idx + 1,);
97 pub fn check(path: &Path, bad: &mut bool) {
98 walk(path, &mut filter_dirs, &mut |entry, contents| {
99 let file = &entry.path().display();
101 let mut lines = contents.lines().enumerate().peekable();
102 while let Some((_, line)) = lines.next() {
103 if line.contains(START_COMMENT) {
104 check_section(file, &mut lines, bad);
105 if lines.peek().is_none() {
106 tidy_error!(bad, "{file}: reached end of file expecting `{END_COMMENT}`")