Skip to content

Commit e5cfc55

Browse files
committed
Auto merge of rust-lang#117149 - nnethercote:tidy-alphabetical-unit-tests, r=Nilstrieb
tidy: add unit tests for alphabetical checks I discovered there aren't any tests while working on rust-lang#117068. r? `@Nilstrieb`
2 parents 7cc36de + a0b0ace commit e5cfc55

File tree

3 files changed

+262
-36
lines changed

3 files changed

+262
-36
lines changed

src/tools/tidy/src/alphabetical.rs

+64-30
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
//! Checks that a list of items is in alphabetical order
22
//!
3-
//! To use, use the following annotation in the code:
3+
//! Use the following marker in the code:
44
//! ```rust
55
//! // tidy-alphabetical-start
66
//! fn aaa() {}
@@ -10,17 +10,23 @@
1010
//! ```
1111
//!
1212
//! The following lines are ignored:
13+
//! - Empty lines
1314
//! - 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
15+
//! - Lines starting with `//`, `#` (except those starting with `#!`), `)`, `]`, `}` if the comment
16+
//! has the same indentation as the first line
17+
//! - Lines starting with a closing delimiter (`)`, `[`, `}`) are ignored.
1618
//!
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+
//! If a line ends with an opening delimiter, we effectively join the following line to it before
20+
//! checking it. E.g. `foo(\nbar)` is treated like `foo(bar)`.
1921
20-
use std::{fmt::Display, path::Path};
22+
use std::fmt::Display;
23+
use std::path::Path;
2124

2225
use crate::walk::{filter_dirs, walk};
2326

27+
#[cfg(test)]
28+
mod tests;
29+
2430
fn indentation(line: &str) -> usize {
2531
line.find(|c| c != ' ').unwrap_or(0)
2632
}
@@ -29,28 +35,36 @@ fn is_close_bracket(c: char) -> bool {
2935
matches!(c, ')' | ']' | '}')
3036
}
3137

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+
const START_MARKER: &str = "tidy-alphabetical-start";
39+
const END_MARKER: &str = "tidy-alphabetical-end";
3540

3641
fn check_section<'a>(
3742
file: impl Display,
3843
lines: impl Iterator<Item = (usize, &'a str)>,
44+
err: &mut dyn FnMut(&str) -> std::io::Result<()>,
3945
bad: &mut bool,
4046
) {
41-
let content_lines = lines.take_while(|(_, line)| !line.contains(END_COMMENT));
42-
4347
let mut prev_line = String::new();
4448
let mut first_indent = None;
4549
let mut in_split_line = None;
4650

47-
for (line_idx, line) in content_lines {
48-
if line.contains(START_COMMENT) {
49-
tidy_error!(
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,
5059
bad,
51-
"{file}:{} found `{START_COMMENT}` expecting `{END_COMMENT}`",
52-
line_idx
53-
)
60+
"{file}:{} found `{START_MARKER}` expecting `{END_MARKER}`",
61+
idx + 1
62+
);
63+
return;
64+
}
65+
66+
if line.contains(END_MARKER) {
67+
return;
5468
}
5569

5670
let indent = first_indent.unwrap_or_else(|| {
@@ -60,6 +74,7 @@ fn check_section<'a>(
6074
});
6175

6276
let line = if let Some(prev_split_line) = in_split_line {
77+
// Join the split lines.
6378
in_split_line = None;
6479
format!("{prev_split_line}{}", line.trim_start())
6580
} else {
@@ -73,7 +88,7 @@ fn check_section<'a>(
7388
let trimmed_line = line.trim_start_matches(' ');
7489

7590
if trimmed_line.starts_with("//")
76-
|| trimmed_line.starts_with("#[")
91+
|| (trimmed_line.starts_with("#") && !trimmed_line.starts_with("#!"))
7792
|| trimmed_line.starts_with(is_close_bracket)
7893
{
7994
continue;
@@ -87,25 +102,44 @@ fn check_section<'a>(
87102
let prev_line_trimmed_lowercase = prev_line.trim_start_matches(' ').to_lowercase();
88103

89104
if trimmed_line.to_lowercase() < prev_line_trimmed_lowercase {
90-
tidy_error!(bad, "{file}:{}: line not in alphabetical order", line_idx + 1,);
105+
tidy_error_ext!(err, bad, "{file}:{}: line not in alphabetical order", idx + 1);
91106
}
92107

93108
prev_line = line;
94109
}
110+
111+
tidy_error_ext!(err, bad, "{file}: reached end of file expecting `{END_MARKER}`")
95112
}
96113

97-
pub fn check(path: &Path, bad: &mut bool) {
98-
walk(path, |path, _is_dir| filter_dirs(path), &mut |entry, contents| {
99-
let file = &entry.path().display();
114+
fn 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+
}
100129

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}`")
107-
}
108-
}
130+
if line.contains(START_MARKER) {
131+
check_section(file, &mut lines, err, bad);
109132
}
133+
}
134+
}
135+
136+
pub 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)
110144
});
111145
}
+188
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
use super::*;
2+
use std::io::Write;
3+
use std::str::from_utf8;
4+
5+
fn test(lines: &str, name: &str, expected_msg: &str, expected_bad: bool) {
6+
let mut actual_msg = Vec::new();
7+
let mut actual_bad = false;
8+
let mut err = |args: &_| {
9+
write!(&mut actual_msg, "{args}")?;
10+
Ok(())
11+
};
12+
check_lines(&name, lines.lines().enumerate(), &mut err, &mut actual_bad);
13+
assert_eq!(expected_msg, from_utf8(&actual_msg).unwrap());
14+
assert_eq!(expected_bad, actual_bad);
15+
}
16+
17+
fn good(lines: &str) {
18+
test(lines, "good", "", false);
19+
}
20+
21+
fn bad(lines: &str, expected_msg: &str) {
22+
test(lines, "bad", expected_msg, true);
23+
}
24+
25+
#[test]
26+
fn test_no_markers() {
27+
let lines = "\
28+
def
29+
abc
30+
xyz
31+
";
32+
good(lines);
33+
}
34+
35+
#[test]
36+
fn test_rust_good() {
37+
let lines = "\
38+
// tidy-alphabetical-start
39+
abc
40+
def
41+
xyz
42+
// tidy-alphabetical-end"; // important: end marker on last line
43+
good(lines);
44+
}
45+
46+
#[test]
47+
fn test_complex_good() {
48+
let lines = "\
49+
zzz
50+
51+
// tidy-alphabetical-start
52+
abc
53+
// Rust comments are ok
54+
def
55+
# TOML comments are ok
56+
xyz
57+
// tidy-alphabetical-end
58+
59+
# tidy-alphabetical-start
60+
foo(abc);
61+
// blank lines are ok
62+
63+
// split line gets joined
64+
foo(
65+
def
66+
);
67+
68+
foo(xyz);
69+
# tidy-alphabetical-end
70+
71+
% tidy-alphabetical-start
72+
abc
73+
ignored_due_to_different_indent
74+
def
75+
% tidy-alphabetical-end
76+
77+
aaa
78+
";
79+
good(lines);
80+
}
81+
82+
#[test]
83+
fn test_rust_bad() {
84+
let lines = "\
85+
// tidy-alphabetical-start
86+
abc
87+
xyz
88+
def
89+
// tidy-alphabetical-end
90+
";
91+
bad(lines, "bad:4: line not in alphabetical order");
92+
}
93+
94+
#[test]
95+
fn test_toml_bad() {
96+
let lines = "\
97+
# tidy-alphabetical-start
98+
abc
99+
xyz
100+
def
101+
# tidy-alphabetical-end
102+
";
103+
bad(lines, "bad:4: line not in alphabetical order");
104+
}
105+
106+
#[test]
107+
fn test_features_bad() {
108+
// Even though lines starting with `#` are treated as comments, lines
109+
// starting with `#!` are an exception.
110+
let lines = "\
111+
tidy-alphabetical-start
112+
#![feature(abc)]
113+
#![feature(xyz)]
114+
#![feature(def)]
115+
tidy-alphabetical-end
116+
";
117+
bad(lines, "bad:4: line not in alphabetical order");
118+
}
119+
120+
#[test]
121+
fn test_indent_bad() {
122+
// All lines are indented the same amount, and so are checked.
123+
let lines = "\
124+
$ tidy-alphabetical-start
125+
abc
126+
xyz
127+
def
128+
$ tidy-alphabetical-end
129+
";
130+
bad(lines, "bad:4: line not in alphabetical order");
131+
}
132+
133+
#[test]
134+
fn test_split_bad() {
135+
let lines = "\
136+
|| tidy-alphabetical-start
137+
foo(abc)
138+
foo(
139+
xyz
140+
)
141+
foo(
142+
def
143+
)
144+
&& tidy-alphabetical-end
145+
";
146+
bad(lines, "bad:7: line not in alphabetical order");
147+
}
148+
149+
#[test]
150+
fn test_double_start() {
151+
let lines = "\
152+
tidy-alphabetical-start
153+
abc
154+
tidy-alphabetical-start
155+
";
156+
bad(lines, "bad:3 found `tidy-alphabetical-start` expecting `tidy-alphabetical-end`");
157+
}
158+
159+
#[test]
160+
fn test_missing_start() {
161+
let lines = "\
162+
abc
163+
tidy-alphabetical-end
164+
abc
165+
";
166+
bad(lines, "bad:2 found `tidy-alphabetical-end` expecting `tidy-alphabetical-start`");
167+
}
168+
169+
#[test]
170+
fn test_missing_end() {
171+
let lines = "\
172+
tidy-alphabetical-start
173+
abc
174+
";
175+
bad(lines, "bad: reached end of file expecting `tidy-alphabetical-end`");
176+
}
177+
178+
#[test]
179+
fn test_double_end() {
180+
let lines = "\
181+
tidy-alphabetical-start
182+
abc
183+
tidy-alphabetical-end
184+
def
185+
tidy-alphabetical-end
186+
";
187+
bad(lines, "bad:5 found `tidy-alphabetical-end` expecting `tidy-alphabetical-start`");
188+
}

src/tools/tidy/src/lib.rs

+10-6
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,6 @@
33
//! This library contains the tidy lints and exposes it
44
//! to be used by tools.
55
6-
use std::fmt::Display;
7-
86
use termcolor::WriteColor;
97

108
/// A helper macro to `unwrap` a result except also print out details like:
@@ -31,16 +29,22 @@ macro_rules! t {
3129

3230
macro_rules! tidy_error {
3331
($bad:expr, $($fmt:tt)*) => ({
34-
$crate::tidy_error($bad, format_args!($($fmt)*)).expect("failed to output error");
32+
$crate::tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
33+
*$bad = true;
3534
});
3635
}
3736

38-
fn tidy_error(bad: &mut bool, args: impl Display) -> std::io::Result<()> {
37+
macro_rules! tidy_error_ext {
38+
($tidy_error:path, $bad:expr, $($fmt:tt)*) => ({
39+
$tidy_error(&format_args!($($fmt)*).to_string()).expect("failed to output error");
40+
*$bad = true;
41+
});
42+
}
43+
44+
fn tidy_error(args: &str) -> std::io::Result<()> {
3945
use std::io::Write;
4046
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream};
4147

42-
*bad = true;
43-
4448
let mut stderr = StandardStream::stdout(ColorChoice::Auto);
4549
stderr.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
4650

0 commit comments

Comments
 (0)