]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/unindent_comments.rs
Rollup merge of #89255 - FabianWolff:issue-88806, r=cjgillot
[rust.git] / src / librustdoc / passes / unindent_comments.rs
1 use std::cmp;
2
3 use crate::clean::{self, DocFragment, DocFragmentKind, Item};
4 use crate::core::DocContext;
5 use crate::fold::{self, DocFolder};
6 use crate::passes::Pass;
7
8 #[cfg(test)]
9 mod tests;
10
11 crate const UNINDENT_COMMENTS: Pass = Pass {
12     name: "unindent-comments",
13     run: unindent_comments,
14     description: "removes excess indentation on comments in order for markdown to like it",
15 };
16
17 crate fn unindent_comments(krate: clean::Crate, _: &mut DocContext<'_>) -> clean::Crate {
18     CommentCleaner.fold_crate(krate)
19 }
20
21 struct CommentCleaner;
22
23 impl fold::DocFolder for CommentCleaner {
24     fn fold_item(&mut self, mut i: Item) -> Option<Item> {
25         i.attrs.unindent_doc_comments();
26         Some(self.fold_item_recur(i))
27     }
28 }
29
30 impl clean::Attributes {
31     crate fn unindent_doc_comments(&mut self) {
32         unindent_fragments(&mut self.doc_strings);
33     }
34 }
35
36 fn unindent_fragments(docs: &mut Vec<DocFragment>) {
37     // `add` is used in case the most common sugared doc syntax is used ("/// "). The other
38     // fragments kind's lines are never starting with a whitespace unless they are using some
39     // markdown formatting requiring it. Therefore, if the doc block have a mix between the two,
40     // we need to take into account the fact that the minimum indent minus one (to take this
41     // whitespace into account).
42     //
43     // For example:
44     //
45     // /// hello!
46     // #[doc = "another"]
47     //
48     // In this case, you want "hello! another" and not "hello!  another".
49     let add = if docs.windows(2).any(|arr| arr[0].kind != arr[1].kind)
50         && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
51     {
52         // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
53         // "decide" how much the minimum indent will be.
54         1
55     } else {
56         0
57     };
58
59     // `min_indent` is used to know how much whitespaces from the start of each lines must be
60     // removed. Example:
61     //
62     // ///     hello!
63     // #[doc = "another"]
64     //
65     // In here, the `min_indent` is 1 (because non-sugared fragment are always counted with minimum
66     // 1 whitespace), meaning that "hello!" will be considered a codeblock because it starts with 4
67     // (5 - 1) whitespaces.
68     let min_indent = match docs
69         .iter()
70         .map(|fragment| {
71             fragment.doc.as_str().lines().fold(usize::MAX, |min_indent, line| {
72                 if line.chars().all(|c| c.is_whitespace()) {
73                     min_indent
74                 } else {
75                     // Compare against either space or tab, ignoring whether they are
76                     // mixed or not.
77                     let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
78                     cmp::min(min_indent, whitespace)
79                         + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }
80                 }
81             })
82         })
83         .min()
84     {
85         Some(x) => x,
86         None => return,
87     };
88
89     for fragment in docs {
90         if fragment.doc.as_str().lines().count() == 0 {
91             continue;
92         }
93
94         let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
95             min_indent - add
96         } else {
97             min_indent
98         };
99
100         fragment.indent = min_indent;
101     }
102 }