rulewright 0.1.2

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

#[rustfmt::skip]
const EXAMPLES: &[Example] = &[
    Example {
        label: "bare TODO",
        code: "// TODO: fix this later",
        pass: false,
    },
    Example {
        label: "tracked TODO",
        code: "// TODO(#123) fix this",
        pass: true,
    },
    Example {
        label: "bare FIXME",
        code: "// FIXME this is broken",
        pass: false,
    },
    Example {
        label: "bare HACK",
        code: "// HACK: workaround",
        pass: false,
    },
    Example {
        label: "bare XXX",
        code: "// XXX",
        pass: false,
    },
    Example {
        label: "no comment",
        code: "let todo = 5;",
        pass: true,
    },
    Example {
        label: "inline TODO",
        code: "let x = 1; // TODO fix",
        pass: false,
    },
    Example {
        label: "tracked FIXME",
        code: "// FIXME(perf regression in v2)",
        pass: true,
    },
];

crate::line_rule!(
    todo,
    "Require TODO/FIXME/HACK/XXX to have parenthesized context.",
    "A bare TODO becomes permanent because nobody knows the intended follow-up. Add a real issue, owner, constraint, or removal condition in parentheses; do not invent placeholder metadata just to satisfy the syntax.",
);

const BANNED: &[&str] = &["TODO", "FIXME", "HACK", "XXX"];

fn check_todo(ctx: &FileCtx<'_>) -> Vec<Violation> {
    let mut out = Vec::new();

    for (i, line) in ctx.lines.iter().enumerate() {
        let Some(comment) = parse::find_comment_start(line) else {
            continue;
        };

        for keyword in BANNED {
            if let Some(after) = parse::find_keyword_suffix(comment, keyword) {
                if parse::matches(after, '(') {
                    continue;
                }

                out.push(violation(
                    ctx.rel,
                    i + 1,
                    format!(
                        "{keyword} without tracking context \
                         (use {keyword}(#issue) or {keyword}(reason))"
                    ),
                ));
            }
        }
    }

    out
}

crate::rulewright_test!(check_todo, {
    crate::example_tests!(EXAMPLES, check_todo);
});