Skip to main content

wowlab_tidy/languages/rust/rules/style/
banner_comments.rs

1use 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});