]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes/unindent_comments.rs
Bump to 1.33.0
[rust.git] / src / librustdoc / passes / unindent_comments.rs
1 // Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
2 // file at the top-level directory of this distribution and at
3 // http://rust-lang.org/COPYRIGHT.
4 //
5 // Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6 // http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7 // <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8 // option. This file may not be copied, modified, or distributed
9 // except according to those terms.
10
11 use std::cmp;
12 use std::string::String;
13 use std::usize;
14
15 use clean::{self, DocFragment, Item};
16 use fold::{self, DocFolder};
17 use passes::Pass;
18
19 pub const UNINDENT_COMMENTS: Pass =
20     Pass::late("unindent-comments", unindent_comments,
21         "removes excess indentation on comments in order for markdown to like it");
22
23 pub fn unindent_comments(krate: clean::Crate) -> clean::Crate {
24     CommentCleaner.fold_crate(krate)
25 }
26
27 struct CommentCleaner;
28
29 impl fold::DocFolder for CommentCleaner {
30     fn fold_item(&mut self, mut i: Item) -> Option<Item> {
31         i.attrs.unindent_doc_comments();
32         self.fold_item_recur(i)
33     }
34 }
35
36 impl clean::Attributes {
37     pub fn unindent_doc_comments(&mut self) {
38         unindent_fragments(&mut self.doc_strings);
39     }
40 }
41
42 fn unindent_fragments(docs: &mut Vec<DocFragment>) {
43     for fragment in docs {
44         match *fragment {
45             DocFragment::SugaredDoc(_, _, ref mut doc_string) |
46             DocFragment::RawDoc(_, _, ref mut doc_string) |
47             DocFragment::Include(_, _, _, ref mut doc_string) =>
48                 *doc_string = unindent(doc_string),
49         }
50     }
51 }
52
53 fn unindent(s: &str) -> String {
54     let lines = s.lines().collect::<Vec<&str> >();
55     let mut saw_first_line = false;
56     let mut saw_second_line = false;
57     let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
58
59         // After we see the first non-whitespace line, look at
60         // the line we have. If it is not whitespace, and therefore
61         // part of the first paragraph, then ignore the indentation
62         // level of the first line
63         let ignore_previous_indents =
64             saw_first_line &&
65             !saw_second_line &&
66             !line.chars().all(|c| c.is_whitespace());
67
68         let min_indent = if ignore_previous_indents {
69             usize::MAX
70         } else {
71             min_indent
72         };
73
74         if saw_first_line {
75             saw_second_line = true;
76         }
77
78         if line.chars().all(|c| c.is_whitespace()) {
79             min_indent
80         } else {
81             saw_first_line = true;
82             let mut whitespace = 0;
83             line.chars().all(|char| {
84                 // Compare against either space or tab, ignoring whether they
85                 // are mixed or not
86                 if char == ' ' || char == '\t' {
87                     whitespace += 1;
88                     true
89                 } else {
90                     false
91                 }
92             });
93             cmp::min(min_indent, whitespace)
94         }
95     });
96
97     if !lines.is_empty() {
98         let mut unindented = vec![ lines[0].trim_start().to_string() ];
99         unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
100             if line.chars().all(|c| c.is_whitespace()) {
101                 line.to_string()
102             } else {
103                 assert!(line.len() >= min_indent);
104                 line[min_indent..].to_string()
105             }
106         }).collect::<Vec<_>>());
107         unindented.join("\n")
108     } else {
109         s.to_string()
110     }
111 }
112
113 #[cfg(test)]
114 mod unindent_tests {
115     use super::unindent;
116
117     #[test]
118     fn should_unindent() {
119         let s = "    line1\n    line2".to_string();
120         let r = unindent(&s);
121         assert_eq!(r, "line1\nline2");
122     }
123
124     #[test]
125     fn should_unindent_multiple_paragraphs() {
126         let s = "    line1\n\n    line2".to_string();
127         let r = unindent(&s);
128         assert_eq!(r, "line1\n\nline2");
129     }
130
131     #[test]
132     fn should_leave_multiple_indent_levels() {
133         // Line 2 is indented another level beyond the
134         // base indentation and should be preserved
135         let s = "    line1\n\n        line2".to_string();
136         let r = unindent(&s);
137         assert_eq!(r, "line1\n\n    line2");
138     }
139
140     #[test]
141     fn should_ignore_first_line_indent() {
142         // The first line of the first paragraph may not be indented as
143         // far due to the way the doc string was written:
144         //
145         // #[doc = "Start way over here
146         //          and continue here"]
147         let s = "line1\n    line2".to_string();
148         let r = unindent(&s);
149         assert_eq!(r, "line1\nline2");
150     }
151
152     #[test]
153     fn should_not_ignore_first_line_indent_in_a_single_line_para() {
154         let s = "line1\n\n    line2".to_string();
155         let r = unindent(&s);
156         assert_eq!(r, "line1\n\n    line2");
157     }
158
159     #[test]
160     fn should_unindent_tabs() {
161         let s = "\tline1\n\tline2".to_string();
162         let r = unindent(&s);
163         assert_eq!(r, "line1\nline2");
164     }
165
166     #[test]
167     fn should_trim_mixed_indentation() {
168         let s = "\t    line1\n\t    line2".to_string();
169         let r = unindent(&s);
170         assert_eq!(r, "line1\nline2");
171
172         let s = "    \tline1\n    \tline2".to_string();
173         let r = unindent(&s);
174         assert_eq!(r, "line1\nline2");
175     }
176
177     #[test]
178     fn should_not_trim() {
179         let s = "\t    line1  \n\t    line2".to_string();
180         let r = unindent(&s);
181         assert_eq!(r, "line1  \nline2");
182
183         let s = "    \tline1  \n    \tline2".to_string();
184         let r = unindent(&s);
185         assert_eq!(r, "line1  \nline2");
186     }
187 }