]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/unindent_comments.rs
Rollup merge of #60492 - acrrd:issues/54054_chain, r=SimonSapin
[rust.git] / src / librustdoc / passes / unindent_comments.rs
1 use std::cmp;
2 use std::string::String;
3 use std::usize;
4
5 use crate::clean::{self, DocFragment, Item};
6 use crate::core::DocContext;
7 use crate::fold::{self, DocFolder};
8 use crate::passes::Pass;
9
10 #[cfg(test)]
11 mod tests;
12
13 pub const UNINDENT_COMMENTS: Pass = Pass {
14     name: "unindent-comments",
15     pass: unindent_comments,
16     description: "removes excess indentation on comments in order for markdown to like it",
17 };
18
19 pub fn unindent_comments(krate: clean::Crate, _: &DocContext<'_>) -> clean::Crate {
20     CommentCleaner.fold_crate(krate)
21 }
22
23 struct CommentCleaner;
24
25 impl fold::DocFolder for CommentCleaner {
26     fn fold_item(&mut self, mut i: Item) -> Option<Item> {
27         i.attrs.unindent_doc_comments();
28         self.fold_item_recur(i)
29     }
30 }
31
32 impl clean::Attributes {
33     pub fn unindent_doc_comments(&mut self) {
34         unindent_fragments(&mut self.doc_strings);
35     }
36 }
37
38 fn unindent_fragments(docs: &mut Vec<DocFragment>) {
39     for fragment in docs {
40         match *fragment {
41             DocFragment::SugaredDoc(_, _, ref mut doc_string) |
42             DocFragment::RawDoc(_, _, ref mut doc_string) |
43             DocFragment::Include(_, _, _, ref mut doc_string) =>
44                 *doc_string = unindent(doc_string),
45         }
46     }
47 }
48
49 fn unindent(s: &str) -> String {
50     let lines = s.lines().collect::<Vec<&str> >();
51     let mut saw_first_line = false;
52     let mut saw_second_line = false;
53     let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
54
55         // After we see the first non-whitespace line, look at
56         // the line we have. If it is not whitespace, and therefore
57         // part of the first paragraph, then ignore the indentation
58         // level of the first line
59         let ignore_previous_indents =
60             saw_first_line &&
61             !saw_second_line &&
62             !line.chars().all(|c| c.is_whitespace());
63
64         let min_indent = if ignore_previous_indents {
65             usize::MAX
66         } else {
67             min_indent
68         };
69
70         if saw_first_line {
71             saw_second_line = true;
72         }
73
74         if line.chars().all(|c| c.is_whitespace()) {
75             min_indent
76         } else {
77             saw_first_line = true;
78             let mut whitespace = 0;
79             line.chars().all(|char| {
80                 // Compare against either space or tab, ignoring whether they
81                 // are mixed or not
82                 if char == ' ' || char == '\t' {
83                     whitespace += 1;
84                     true
85                 } else {
86                     false
87                 }
88             });
89             cmp::min(min_indent, whitespace)
90         }
91     });
92
93     if !lines.is_empty() {
94         let mut unindented = vec![ lines[0].trim_start().to_string() ];
95         unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
96             if line.chars().all(|c| c.is_whitespace()) {
97                 line.to_string()
98             } else {
99                 assert!(line.len() >= min_indent);
100                 line[min_indent..].to_string()
101             }
102         }).collect::<Vec<_>>());
103         unindented.join("\n")
104     } else {
105         s.to_string()
106     }
107 }