utoipa_gen/doc_comment.rs
1use syn::{Attribute, Expr, Lit, Meta};
2
3const DOC_ATTRIBUTE_TYPE: &str = "doc";
4
5/// CommentAttributes holds Vec of parsed doc comments
6#[cfg_attr(feature = "debug", derive(Debug))]
7pub(crate) struct CommentAttributes(pub(crate) Vec<String>);
8
9impl CommentAttributes {
10 /// Creates new [`CommentAttributes`] instance from [`Attribute`] slice filtering out all
11 /// other attributes which are not `doc` comments
12 pub(crate) fn from_attributes(attributes: &[Attribute]) -> Self {
13 let mut docs = attributes
14 .iter()
15 .filter_map(|attr| {
16 if !matches!(attr.path().get_ident(), Some(ident) if ident == DOC_ATTRIBUTE_TYPE) {
17 return None;
18 }
19 // ignore `#[doc(hidden)]` and similar tags.
20 if let Meta::NameValue(name_value) = &attr.meta {
21 if let Expr::Lit(ref doc_comment) = name_value.value {
22 if let Lit::Str(ref doc) = doc_comment.lit {
23 let mut doc = doc.value();
24 // NB. Only trim trailing whitespaces. Leading whitespaces are handled
25 // below.
26 doc.truncate(doc.trim_end().len());
27 return Some(doc);
28 }
29 }
30 }
31 None
32 })
33 .collect::<Vec<_>>();
34 // Calculate the minimum indentation of all non-empty lines and strip them.
35 // This can get rid of typical single space after doc comment start `///`, but not messing
36 // up indentation of markdown list or code.
37 let min_indent = docs
38 .iter()
39 .filter(|s| !s.is_empty())
40 // Only recognize ASCII space, not unicode multi-bytes ones.
41 // `str::trim_ascii_start` requires 1.80 which is greater than our MSRV yet.
42 .map(|s| s.len() - s.trim_start_matches(' ').len())
43 .min()
44 .unwrap_or(0);
45 for line in &mut docs {
46 if !line.is_empty() {
47 line.drain(..min_indent);
48 }
49 }
50 Self(docs)
51 }
52
53 pub(crate) fn is_empty(&self) -> bool {
54 self.0.is_empty()
55 }
56
57 /// Returns found `doc comments` as formatted `String` joining them all with `\n` _(new line)_.
58 pub(crate) fn as_formatted_string(&self) -> String {
59 self.0.join("\n")
60 }
61}