|
| 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 | +fn check_section<'a>( |
| 33 | + file: impl Display, |
| 34 | + lines: impl Iterator<Item = (usize, &'a str)>, |
| 35 | + bad: &mut bool, |
| 36 | +) { |
| 37 | + let content_lines = lines.take_while(|(_, line)| !line.contains("// tidy-alphabetical-end")); |
| 38 | + |
| 39 | + let mut prev_line = String::new(); |
| 40 | + let mut first_indent = None; |
| 41 | + let mut in_split_line = None; |
| 42 | + |
| 43 | + for (line_idx, line) in content_lines { |
| 44 | + let indent = first_indent.unwrap_or_else(|| { |
| 45 | + let indent = indentation(line); |
| 46 | + first_indent = Some(indent); |
| 47 | + indent |
| 48 | + }); |
| 49 | + |
| 50 | + let line = if let Some(prev_split_line) = in_split_line { |
| 51 | + in_split_line = None; |
| 52 | + format!("{prev_split_line}{}", line.trim_start()) |
| 53 | + } else { |
| 54 | + line.to_string() |
| 55 | + }; |
| 56 | + |
| 57 | + if indentation(&line) != indent { |
| 58 | + continue; |
| 59 | + } |
| 60 | + |
| 61 | + let trimmed_line = line.trim_start_matches(' '); |
| 62 | + |
| 63 | + if trimmed_line.starts_with("//") |
| 64 | + || trimmed_line.starts_with("#[") |
| 65 | + || trimmed_line.starts_with(is_close_bracket) |
| 66 | + { |
| 67 | + continue; |
| 68 | + } |
| 69 | + |
| 70 | + if line.trim_end().ends_with('(') { |
| 71 | + in_split_line = Some(line); |
| 72 | + continue; |
| 73 | + } |
| 74 | + |
| 75 | + let prev_line_trimmed_lowercase = prev_line.trim_start_matches(' ').to_lowercase(); |
| 76 | + |
| 77 | + if trimmed_line.to_lowercase() < prev_line_trimmed_lowercase { |
| 78 | + tidy_error!(bad, "{file}:{}: line not in alphabetical order", line_idx + 1,); |
| 79 | + } |
| 80 | + |
| 81 | + prev_line = line; |
| 82 | + } |
| 83 | +} |
| 84 | + |
| 85 | +const START_COMMENT: &str = "// tidy-alphabetical-start"; |
| 86 | + |
| 87 | +pub fn check(path: &Path, bad: &mut bool) { |
| 88 | + walk(path, &mut filter_dirs, &mut |entry, contents| { |
| 89 | + let file = &entry.path().display(); |
| 90 | + |
| 91 | + let mut lines = contents.lines().enumerate(); |
| 92 | + while let Some((_, line)) = lines.next() { |
| 93 | + if line.contains(START_COMMENT) { |
| 94 | + check_section(file, &mut lines, bad); |
| 95 | + } |
| 96 | + } |
| 97 | + }); |
| 98 | +} |
0 commit comments