rulewright 0.1.1

Executable engineering standards for Rust workspaces
Documentation
use crate::{Example, FileCtx, Fix, Violation, infra::parse, violation};

const MIN_DECORATIVE_RUN: usize = 4;

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "plain separator",
        code: "// ----------------",
        pass: false,
    },
    Example {
        label: "framed banner",
        code: "// ==== parsing ====",
        pass: false,
    },
    Example {
        label: "mixed decorative separator",
        code: "// __~~__",
        pass: false,
    },
    Example {
        label: "ordinary comment",
        code: "// Parsing helpers live below.",
        pass: true,
    },
    Example {
        label: "short punctuation",
        code: "// --- note ---",
        pass: true,
    },
    Example {
        label: "comment-like string",
        code: "let banner = \"// --------\";",
        pass: true,
    },
];

crate::line_rule!(
    banner_comments,
    "Disallow decorative separator and framed banner comments.",
    "Structural code organization communicates sections more clearly than punctuation banners.",
    Low,
    fix_banner_comments,
);

fn check_banner_comments(ctx: &FileCtx<'_>) -> Vec<Violation> {
    ctx.lines
        .iter()
        .enumerate()
        .filter_map(|(index, line)| {
            let trimmed = line.trim();

            if !parse::is_comment(trimmed)
                || trimmed.starts_with("///")
                || trimmed.starts_with("//!")
            {
                return None;
            }

            let content = trimmed.strip_prefix("//")?.trim();

            is_banner(content).then(|| violation(ctx.rel, index + 1, "decorative banner comment"))
        })
        .collect()
}

fn is_banner(content: &str) -> bool {
    let chars: Vec<char> = content.chars().collect();

    if chars.is_empty() {
        return false;
    }

    let only_decorative = chars
        .iter()
        .all(|ch| ch.is_whitespace() || is_decorative(*ch));
    let leading = chars.iter().take_while(|ch| is_decorative(**ch)).count();
    let trailing = chars
        .iter()
        .rev()
        .take_while(|ch| is_decorative(**ch))
        .count();

    (only_decorative && longest_run(&chars) >= MIN_DECORATIVE_RUN)
        || (leading >= MIN_DECORATIVE_RUN && trailing >= MIN_DECORATIVE_RUN)
}

fn is_decorative(ch: char) -> bool {
    matches!(ch, '-' | '=' | '*' | '#' | '~' | '_')
}

fn longest_run(chars: &[char]) -> usize {
    let mut longest = 0;
    let mut current = 0;

    for ch in chars {
        if is_decorative(*ch) {
            current += 1;
            longest = longest.max(current);
        } else {
            current = 0;
        }
    }

    longest
}

#[expect(
    clippy::unnecessary_wraps,
    reason = "line-rule fixer callbacks uniformly return Option<Fix>"
)]
fn fix_banner_comments(_ctx: &FileCtx<'_>, violation: &Violation) -> Option<Fix> {
    Some(Fix::delete(violation.line, violation.line))
}

crate::rulewright_test!(check_banner_comments, {
    crate::example_tests!(EXAMPLES, check_banner_comments);
    crate::fix_tests!(line, check_banner_comments, fix_banner_comments);
});