Skip to main content

wowlab_tidy/languages/rust/rules/docs/
doc_param_table.rs

1use crate::{Example, FileCtx, Violation, infra::parse, violation};
2
3/// Pass/fail cases for `example_tests!`.
4#[rustfmt::skip]
5const EXAMPLES: &[Example] = &[
6    Example {
7        label: "Parameters section",
8        code: "/// # Parameters",
9        pass: false,
10    },
11    Example {
12        label: "Arguments section",
13        code: "/// # Arguments",
14        pass: false,
15    },
16    Example {
17        label: "Params section at deeper level",
18        code: "/// ## Params",
19        pass: false,
20    },
21    Example {
22        label: "inner doc Arguments section",
23        code: "//! # Arguments",
24        pass: false,
25    },
26    Example {
27        label: "lowercase heading",
28        code: "/// # arguments",
29        pass: false,
30    },
31    Example {
32        label: "Examples section",
33        code: "/// # Examples",
34        pass: true,
35    },
36    Example {
37        label: "Type Parameters heading is not a parameter table",
38        code: "/// # Type Parameters",
39        pass: true,
40    },
41    Example {
42        label: "heading inside code fence",
43        code: "/// ```text\n/// # Arguments\n/// ```",
44        pass: true,
45    },
46    Example {
47        label: "plain comment is not a doc comment",
48        code: "// # Arguments",
49        pass: true,
50    },
51    Example {
52        label: "prose mention of arguments",
53        code: "/// Takes two arguments and merges them.",
54        pass: true,
55    },
56];
57
58crate::line_rule!(
59    doc_param_table,
60    "Ban `# Parameters`/`# Arguments`/`# Params` sections in doc comments.",
61    "Rust docs explain parameters in prose, not tables; parameter sections duplicate the signature (M-CANONICAL-DOCS).",
62    Low,
63);
64
65/// Section names that indicate a parameter table.
66const HEADINGS: &[&str] = &["parameters", "arguments", "params"];
67
68fn check_doc_param_table(ctx: &FileCtx<'_>) -> Vec<Violation> {
69    let mut out = Vec::new();
70    let mut in_fence = false;
71
72    for (i, line) in ctx.lines.iter().enumerate() {
73        let Some(raw) = parse::doc_comment_content(line.trim()) else {
74            in_fence = false;
75            continue;
76        };
77        let text = raw.trim();
78
79        if parse::matches(text, "```") {
80            in_fence = !in_fence;
81            continue;
82        }
83
84        if in_fence || !is_param_heading(text) {
85            continue;
86        }
87
88        out.push(violation(
89            ctx.rel,
90            i + 1,
91            "explain parameters in prose, not a `# Parameters`-style doc section",
92        ));
93    }
94
95    out
96}
97
98fn is_param_heading(text: &str) -> bool {
99    let stripped = text.trim_start_matches('#');
100
101    if stripped.len() == text.len() {
102        return false;
103    }
104
105    let name = stripped.trim();
106
107    HEADINGS.iter().any(|h| name.eq_ignore_ascii_case(h))
108}
109
110crate::tidy_test!(check_doc_param_table, {
111    crate::example_tests!(EXAMPLES, check_doc_param_table);
112});