wowlab_tidy/languages/rust/rules/api/
pub_api_docs.rs1use ra_ap_syntax::{
2 AstNode,
3 ast::{self, HasAttrs, HasDocComments, HasName, HasVisibility, VisibilityKind},
4};
5
6use crate::{AstCtx, Example, Violation};
7
8#[rustfmt::skip]
9const EXAMPLES: &[Example] = &[
10 Example {
11 label: "undocumented pub fn",
12 code: "pub fn foo() {}",
13 pass: false,
14 },
15 Example {
16 label: "documented pub fn",
17 code: "/// Does something.\npub fn foo() {}",
18 pass: true,
19 },
20 Example {
21 label: "private fn",
22 code: "fn foo() {}",
23 pass: true,
24 },
25 Example {
26 label: "pub(crate) fn",
27 code: "pub(crate) fn foo() {}",
28 pass: true,
29 },
30 Example {
31 label: "doc hidden pub fn",
32 code: "#[doc(hidden)]\npub fn foo() {}",
33 pass: true,
34 },
35 Example {
36 label: "undocumented pub struct",
37 code: "pub struct S;",
38 pass: false,
39 },
40 Example {
41 label: "undocumented pub enum",
42 code: "pub enum E { A }",
43 pass: false,
44 },
45 Example {
46 label: "undocumented pub trait",
47 code: "pub trait T {}",
48 pass: false,
49 },
50 Example {
51 label: "undocumented pub type alias",
52 code: "pub type A = u8;",
53 pass: false,
54 },
55 Example {
56 label: "undocumented pub const",
57 code: "pub const C: u8 = 1;",
58 pass: false,
59 },
60 Example {
61 label: "undocumented pub static",
62 code: "pub static S: u8 = 1;",
63 pass: false,
64 },
65 Example {
66 label: "documented pub struct",
67 code: "/// A struct.\npub struct S;",
68 pass: true,
69 },
70 Example {
71 label: "pub(crate) struct not fully public",
72 code: "pub(crate) struct S;",
73 pass: true,
74 },
75 Example {
76 label: "doc hidden pub struct",
77 code: "#[doc(hidden)]\npub struct S;",
78 pass: true,
79 },
80 Example {
81 label: "doc name-value attr pub struct",
82 code: "#[doc = \"x\"]\npub struct S;",
83 pass: true,
84 },
85];
86
87crate::ast_rule!(
88 pub_api_docs,
89 "Require doc comments on public items.",
90 "Undocumented public items force users to read source code. Doc comments generate searchable API documentation.",
91);
92
93fn check_pub_api_docs(ctx: &AstCtx<'_>) -> Vec<Violation> {
94 let functions = ctx
95 .nodes::<ast::Fn>()
96 .filter_map(|item| missing_docs(ctx, &item, "function"));
97 let structs = ctx
98 .nodes::<ast::Struct>()
99 .filter_map(|item| missing_docs(ctx, &item, "struct"));
100 let enums = ctx
101 .nodes::<ast::Enum>()
102 .filter_map(|item| missing_docs(ctx, &item, "enum"));
103 let traits = ctx
104 .nodes::<ast::Trait>()
105 .filter_map(|item| missing_docs(ctx, &item, "trait"));
106 let aliases = ctx
107 .nodes::<ast::TypeAlias>()
108 .filter_map(|item| missing_docs(ctx, &item, "type alias"));
109 let constants = ctx
110 .nodes::<ast::Const>()
111 .filter_map(|item| missing_docs(ctx, &item, "const"));
112 let statics = ctx
113 .nodes::<ast::Static>()
114 .filter_map(|item| missing_docs(ctx, &item, "static"));
115
116 functions
117 .chain(structs)
118 .chain(enums)
119 .chain(traits)
120 .chain(aliases)
121 .chain(constants)
122 .chain(statics)
123 .collect()
124}
125
126fn missing_docs<T>(ctx: &AstCtx<'_>, item: &T, kind: &str) -> Option<Violation>
127where
128 T: AstNode + HasAttrs + HasDocComments + HasName + HasVisibility,
129{
130 if ctx.is_in_test(item)
131 || !item
132 .visibility()
133 .is_some_and(|vis| matches!(vis.kind(), VisibilityKind::Pub))
134 || item.doc_comments().next().is_some()
135 || item
136 .attrs()
137 .any(|attr| attr.simple_name().as_deref() == Some("doc"))
138 {
139 return None;
140 }
141
142 let name = item.name()?;
143
144 Some(ctx.violation(
145 &name,
146 format!("public {kind} `{name}` is missing documentation"),
147 ))
148}
149
150crate::tidy_ast_test!(check_pub_api_docs, {
151 crate::example_tests!(EXAMPLES, check_pub_api_docs);
152});