tidy/
alphabetical.rs
1use std::fmt::Display;
23use std::path::Path;
24
25use crate::walk::{filter_dirs, walk};
26
27#[cfg(test)]
28mod tests;
29
30fn indentation(line: &str) -> usize {
31 line.find(|c| c != ' ').unwrap_or(0)
32}
33
34fn is_close_bracket(c: char) -> bool {
35 matches!(c, ')' | ']' | '}')
36}
37
38const START_MARKER: &str = "tidy-alphabetical-start";
39const END_MARKER: &str = "tidy-alphabetical-end";
40
41fn check_section<'a>(
42 file: impl Display,
43 lines: impl Iterator<Item = (usize, &'a str)>,
44 err: &mut dyn FnMut(&str) -> std::io::Result<()>,
45 bad: &mut bool,
46) {
47 let mut prev_line = String::new();
48 let mut first_indent = None;
49 let mut in_split_line = None;
50
51 for (idx, line) in lines {
52 if line.is_empty() {
53 continue;
54 }
55
56 if line.contains(START_MARKER) {
57 tidy_error_ext!(
58 err,
59 bad,
60 "{file}:{} found `{START_MARKER}` expecting `{END_MARKER}`",
61 idx + 1
62 );
63 return;
64 }
65
66 if line.contains(END_MARKER) {
67 return;
68 }
69
70 let indent = first_indent.unwrap_or_else(|| {
71 let indent = indentation(line);
72 first_indent = Some(indent);
73 indent
74 });
75
76 let line = if let Some(prev_split_line) = in_split_line {
77 in_split_line = None;
79 format!("{prev_split_line}{}", line.trim_start())
80 } else {
81 line.to_string()
82 };
83
84 if indentation(&line) != indent {
85 continue;
86 }
87
88 let trimmed_line = line.trim_start_matches(' ');
89
90 if trimmed_line.starts_with("//")
91 || (trimmed_line.starts_with('#') && !trimmed_line.starts_with("#!"))
92 || trimmed_line.starts_with(is_close_bracket)
93 {
94 continue;
95 }
96
97 if line.trim_end().ends_with('(') {
98 in_split_line = Some(line);
99 continue;
100 }
101
102 let prev_line_trimmed_lowercase = prev_line.trim_start_matches(' ').to_lowercase();
103
104 if trimmed_line.to_lowercase() < prev_line_trimmed_lowercase {
105 tidy_error_ext!(err, bad, "{file}:{}: line not in alphabetical order", idx + 1);
106 }
107
108 prev_line = line;
109 }
110
111 tidy_error_ext!(err, bad, "{file}: reached end of file expecting `{END_MARKER}`")
112}
113
114fn check_lines<'a>(
115 file: &impl Display,
116 mut lines: impl Iterator<Item = (usize, &'a str)>,
117 err: &mut dyn FnMut(&str) -> std::io::Result<()>,
118 bad: &mut bool,
119) {
120 while let Some((idx, line)) = lines.next() {
121 if line.contains(END_MARKER) {
122 tidy_error_ext!(
123 err,
124 bad,
125 "{file}:{} found `{END_MARKER}` expecting `{START_MARKER}`",
126 idx + 1
127 )
128 }
129
130 if line.contains(START_MARKER) {
131 check_section(file, &mut lines, err, bad);
132 }
133 }
134}
135
136pub fn check(path: &Path, bad: &mut bool) {
137 let skip =
138 |path: &_, _is_dir| filter_dirs(path) || path.ends_with("tidy/src/alphabetical/tests.rs");
139
140 walk(path, skip, &mut |entry, contents| {
141 let file = &entry.path().display();
142 let lines = contents.lines().enumerate();
143 check_lines(file, lines, &mut crate::tidy_error, bad)
144 });
145}