wowlab_tidy/languages/rust/rules/hygiene/
todo.rs1use crate::{Example, FileCtx, Violation, infra::parse, violation};
2
3#[rustfmt::skip]
4const EXAMPLES: &[Example] = &[
5 Example {
6 label: "bare TODO",
7 code: "// TODO: fix this later",
8 pass: false,
9 },
10 Example {
11 label: "tracked TODO",
12 code: "// TODO(#123) fix this",
13 pass: true,
14 },
15 Example {
16 label: "bare FIXME",
17 code: "// FIXME this is broken",
18 pass: false,
19 },
20 Example {
21 label: "bare HACK",
22 code: "// HACK: workaround",
23 pass: false,
24 },
25 Example {
26 label: "bare XXX",
27 code: "// XXX",
28 pass: false,
29 },
30 Example {
31 label: "no comment",
32 code: "let todo = 5;",
33 pass: true,
34 },
35 Example {
36 label: "inline TODO",
37 code: "let x = 1; // TODO fix",
38 pass: false,
39 },
40 Example {
41 label: "tracked FIXME",
42 code: "// FIXME(perf regression in v2)",
43 pass: true,
44 },
45];
46
47crate::line_rule!(
48 todo,
49 "Require TODO/FIXME/HACK/XXX to have parenthesized context.",
50 "TODO without context (who, ticket, deadline) becomes permanent. Parenthesized context ensures accountability.",
51);
52
53const BANNED: &[&str] = &["TODO", "FIXME", "HACK", "XXX"];
54
55fn check_todo(ctx: &FileCtx<'_>) -> Vec<Violation> {
56 let mut out = Vec::new();
57
58 for (i, line) in ctx.lines.iter().enumerate() {
59 let Some(comment) = parse::find_comment_start(line) else {
60 continue;
61 };
62
63 for keyword in BANNED {
64 if let Some(after) = parse::find_keyword_suffix(comment, keyword) {
65 if parse::matches(after, '(') {
66 continue;
67 }
68
69 out.push(violation(
70 ctx.rel,
71 i + 1,
72 format!(
73 "{keyword} without tracking context \
74 (use {keyword}(#issue) or {keyword}(reason))"
75 ),
76 ));
77 }
78 }
79 }
80
81 out
82}
83
84crate::tidy_test!(check_todo, {
85 crate::example_tests!(EXAMPLES, check_todo);
86});