wowlab_tidy/languages/rust/rules/docs/
doc_param_table.rs1use crate::{Example, FileCtx, Violation, infra::parse, violation};
2
3#[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
65const 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});