]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/unindent_comments.rs
1c856b1da53f1fd1497aa5442bb261680378fe73
[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 pub 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 pub fn unindent_comments(krate: clean::Crate, _: &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         self.fold_item_recur(i)
27     }
28 }
29
30 impl clean::Attributes {
31     pub 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     let mut saw_first_line = false;
38     let mut saw_second_line = false;
39
40     let add = if !docs.windows(2).all(|arr| arr[0].kind == arr[1].kind)
41         && docs.iter().any(|d| d.kind == DocFragmentKind::SugaredDoc)
42     {
43         // In case we have a mix of sugared doc comments and "raw" ones, we want the sugared one to
44         // "decide" how much the minimum indent will be.
45         1
46     } else {
47         0
48     };
49
50     let min_indent = match docs
51         .iter()
52         .map(|fragment| {
53             fragment.doc.lines().fold(usize::MAX, |min_indent, line| {
54                 // After we see the first non-whitespace line, look at
55                 // the line we have. If it is not whitespace, and therefore
56                 // part of the first paragraph, then ignore the indentation
57                 // level of the first line
58                 let ignore_previous_indents =
59                     saw_first_line && !saw_second_line && !line.chars().all(|c| c.is_whitespace());
60
61                 let min_indent = if ignore_previous_indents { usize::MAX } else { min_indent };
62
63                 if saw_first_line {
64                     saw_second_line = true;
65                 }
66
67                 if line.chars().all(|c| c.is_whitespace()) {
68                     min_indent
69                 } else {
70                     saw_first_line = true;
71                     // Compare against either space or tab, ignoring whether they are
72                     // mixed or not.
73                     let whitespace = line.chars().take_while(|c| *c == ' ' || *c == '\t').count();
74                     cmp::min(min_indent, whitespace)
75                         + if fragment.kind == DocFragmentKind::SugaredDoc { 0 } else { add }
76                 }
77             })
78         })
79         .min()
80     {
81         Some(x) => x,
82         None => return,
83     };
84
85     let mut first_ignored = false;
86     for fragment in docs {
87         let lines: Vec<_> = fragment.doc.lines().collect();
88
89         if !lines.is_empty() {
90             let min_indent = if fragment.kind != DocFragmentKind::SugaredDoc && min_indent > 0 {
91                 min_indent - add
92             } else {
93                 min_indent
94             };
95
96             let mut iter = lines.iter();
97             let mut result = if !first_ignored {
98                 first_ignored = true;
99                 vec![iter.next().unwrap().trim_start().to_string()]
100             } else {
101                 Vec::new()
102             };
103             result.extend_from_slice(
104                 &iter
105                     .map(|&line| {
106                         if line.chars().all(|c| c.is_whitespace()) {
107                             line.to_string()
108                         } else {
109                             assert!(line.len() >= min_indent);
110                             line[min_indent..].to_string()
111                         }
112                     })
113                     .collect::<Vec<_>>(),
114             );
115             fragment.doc = result.join("\n");
116         }
117     }
118 }