wowlab_tidy/languages/rust/rules/style/
banner_comments.rs1use crate::{Example, FileCtx, Fix, Violation, infra::parse, violation};
2
3const MIN_DECORATIVE_RUN: usize = 4;
4
5#[rustfmt::skip]
6const EXAMPLES: &[Example] = &[
7 Example {
8 label: "plain separator",
9 code: "// ----------------",
10 pass: false,
11 },
12 Example {
13 label: "framed banner",
14 code: "// ==== parsing ====",
15 pass: false,
16 },
17 Example {
18 label: "mixed decorative separator",
19 code: "// __~~__",
20 pass: false,
21 },
22 Example {
23 label: "ordinary comment",
24 code: "// Parsing helpers live below.",
25 pass: true,
26 },
27 Example {
28 label: "short punctuation",
29 code: "// --- note ---",
30 pass: true,
31 },
32 Example {
33 label: "comment-like string",
34 code: "let banner = \"// --------\";",
35 pass: true,
36 },
37];
38
39crate::line_rule!(
40 banner_comments,
41 "Disallow decorative separator and framed banner comments.",
42 "Structural code organization communicates sections more clearly than punctuation banners.",
43 Low,
44 fix_banner_comments,
45);
46
47fn check_banner_comments(ctx: &FileCtx<'_>) -> Vec<Violation> {
48 ctx.lines
49 .iter()
50 .enumerate()
51 .filter_map(|(index, line)| {
52 let trimmed = line.trim();
53
54 if !parse::is_comment(trimmed)
55 || trimmed.starts_with("///")
56 || trimmed.starts_with("//!")
57 {
58 return None;
59 }
60
61 let content = trimmed.strip_prefix("//")?.trim();
62
63 is_banner(content).then(|| violation(ctx.rel, index + 1, "decorative banner comment"))
64 })
65 .collect()
66}
67
68fn is_banner(content: &str) -> bool {
69 let chars: Vec<char> = content.chars().collect();
70
71 if chars.is_empty() {
72 return false;
73 }
74
75 let only_decorative = chars
76 .iter()
77 .all(|ch| ch.is_whitespace() || is_decorative(*ch));
78 let leading = chars.iter().take_while(|ch| is_decorative(**ch)).count();
79 let trailing = chars
80 .iter()
81 .rev()
82 .take_while(|ch| is_decorative(**ch))
83 .count();
84
85 (only_decorative && longest_run(&chars) >= MIN_DECORATIVE_RUN)
86 || (leading >= MIN_DECORATIVE_RUN && trailing >= MIN_DECORATIVE_RUN)
87}
88
89fn is_decorative(ch: char) -> bool {
90 matches!(ch, '-' | '=' | '*' | '#' | '~' | '_')
91}
92
93fn longest_run(chars: &[char]) -> usize {
94 let mut longest = 0;
95 let mut current = 0;
96
97 for ch in chars {
98 if is_decorative(*ch) {
99 current += 1;
100 longest = longest.max(current);
101 } else {
102 current = 0;
103 }
104 }
105
106 longest
107}
108
109#[expect(
110 clippy::unnecessary_wraps,
111 reason = "line-rule fixer callbacks uniformly return Option<Fix>"
112)]
113fn fix_banner_comments(_ctx: &FileCtx<'_>, violation: &Violation) -> Option<Fix> {
114 Some(Fix::delete(violation.line, violation.line))
115}
116
117crate::tidy_test!(check_banner_comments, {
118 crate::example_tests!(EXAMPLES, check_banner_comments);
119 crate::fix_tests!(line, check_banner_comments, fix_banner_comments);
120});