]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes.rs
test: Make manual changes to deal with the fallout from removal of
[rust.git] / src / librustdoc / passes.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 collections::HashSet;
13 use std::local_data;
14 use std::uint;
15 use syntax::ast;
16 use rustc::util::nodemap::NodeSet;
17
18 use clean;
19 use clean::Item;
20 use plugins;
21 use fold;
22 use fold::DocFolder;
23
24 /// Strip items marked `#[doc(hidden)]`
25 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
26     let mut stripped = HashSet::new();
27
28     // strip all #[doc(hidden)] items
29     let krate = {
30         struct Stripper<'a> {
31             stripped: &'a mut HashSet<ast::NodeId>
32         };
33         impl<'a> fold::DocFolder for Stripper<'a> {
34             fn fold_item(&mut self, i: Item) -> Option<Item> {
35                 for attr in i.attrs.iter() {
36                     match attr {
37                         &clean::List(ref x, ref l) if "doc" == *x => {
38                             for innerattr in l.iter() {
39                                 match innerattr {
40                                     &clean::Word(ref s) if "hidden" == *s => {
41                                         debug!("found one in strip_hidden; removing");
42                                         self.stripped.insert(i.id);
43                                         return None;
44                                     },
45                                     _ => (),
46                                 }
47                             }
48                         },
49                         _ => ()
50                     }
51                 }
52                 self.fold_item_recur(i)
53             }
54         }
55         let mut stripper = Stripper{ stripped: &mut stripped };
56         stripper.fold_crate(krate)
57     };
58
59     // strip any traits implemented on stripped items
60     let krate = {
61         struct ImplStripper<'a> {
62             stripped: &'a mut HashSet<ast::NodeId>
63         };
64         impl<'a> fold::DocFolder for ImplStripper<'a> {
65             fn fold_item(&mut self, i: Item) -> Option<Item> {
66                 match i.inner {
67                     clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: for_id, .. },
68                                                  .. }) => {
69                         if self.stripped.contains(&for_id) {
70                             return None;
71                         }
72                     }
73                     _ => {}
74                 }
75                 self.fold_item_recur(i)
76             }
77         }
78         let mut stripper = ImplStripper{ stripped: &mut stripped };
79         stripper.fold_crate(krate)
80     };
81
82     (krate, None)
83 }
84
85 /// Strip private items from the point of view of a crate or externally from a
86 /// crate, specified by the `xcrate` flag.
87 pub fn strip_private(krate: clean::Crate) -> plugins::PluginResult {
88     // This stripper collects all *retained* nodes.
89     let mut retained = HashSet::new();
90     let exported_items = local_data::get(super::analysiskey, |analysis| {
91         analysis.unwrap().exported_items.clone()
92     });
93     let mut krate = krate;
94
95     // strip all private items
96     {
97         let mut stripper = Stripper {
98             retained: &mut retained,
99             exported_items: &exported_items,
100         };
101         krate = stripper.fold_crate(krate);
102     }
103
104     // strip all private implementations of traits
105     {
106         let mut stripper = ImplStripper(&retained);
107         krate = stripper.fold_crate(krate);
108     }
109     (krate, None)
110 }
111
112 struct Stripper<'a> {
113     retained: &'a mut HashSet<ast::NodeId>,
114     exported_items: &'a NodeSet,
115 }
116
117 impl<'a> fold::DocFolder for Stripper<'a> {
118     fn fold_item(&mut self, i: Item) -> Option<Item> {
119         match i.inner {
120             // These items can all get re-exported
121             clean::TypedefItem(..) | clean::StaticItem(..) |
122             clean::StructItem(..) | clean::EnumItem(..) |
123             clean::TraitItem(..) | clean::FunctionItem(..) |
124             clean::VariantItem(..) | clean::MethodItem(..) |
125             clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
126                 if !self.exported_items.contains(&i.id) {
127                     return None;
128                 }
129             }
130
131             clean::ViewItemItem(..) |
132             clean::ModuleItem(..) => {
133                 if i.visibility != Some(ast::Public) {
134                     return None
135                 }
136             }
137
138             clean::StructFieldItem(..) => {
139                 if i.visibility == Some(ast::Private) {
140                     return None;
141                 }
142             }
143
144             // trait impls for private items should be stripped
145             clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: ref for_id, .. }, .. }) => {
146                 if !self.exported_items.contains(for_id) {
147                     return None;
148                 }
149             }
150             clean::ImplItem(..) => {}
151
152             // tymethods/macros have no control over privacy
153             clean::MacroItem(..) | clean::TyMethodItem(..) => {}
154         }
155
156         let fastreturn = match i.inner {
157             // nothing left to do for traits (don't want to filter their
158             // methods out, visibility controlled by the trait)
159             clean::TraitItem(..) => true,
160
161             // implementations of traits are always public.
162             clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
163
164             _ => false,
165         };
166
167         let i = if fastreturn {
168             self.retained.insert(i.id);
169             return Some(i);
170         } else {
171             self.fold_item_recur(i)
172         };
173
174         match i {
175             Some(i) => {
176                 match i.inner {
177                     // emptied modules/impls have no need to exist
178                     clean::ModuleItem(ref m)
179                         if m.items.len() == 0 &&
180                            i.doc_value().is_none() => None,
181                     clean::ImplItem(ref i) if i.methods.len() == 0 => None,
182                     _ => {
183                         self.retained.insert(i.id);
184                         Some(i)
185                     }
186                 }
187             }
188             None => None,
189         }
190     }
191 }
192
193 // This stripper discards all private impls of traits
194 struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
195 impl<'a> fold::DocFolder for ImplStripper<'a> {
196     fn fold_item(&mut self, i: Item) -> Option<Item> {
197         match i.inner {
198             clean::ImplItem(ref imp) => {
199                 match imp.trait_ {
200                     Some(clean::ResolvedPath{ id, .. }) => {
201                         let ImplStripper(s) = *self;
202                         if !s.contains(&id) {
203                             return None;
204                         }
205                     }
206                     Some(..) | None => {}
207                 }
208             }
209             _ => {}
210         }
211         self.fold_item_recur(i)
212     }
213 }
214
215
216 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
217     struct CommentCleaner;
218     impl fold::DocFolder for CommentCleaner {
219         fn fold_item(&mut self, i: Item) -> Option<Item> {
220             let mut i = i;
221             let mut avec: Vec<clean::Attribute> = Vec::new();
222             for attr in i.attrs.iter() {
223                 match attr {
224                     &clean::NameValue(ref x, ref s) if "doc" == *x => avec.push(
225                         clean::NameValue(~"doc", unindent(*s))),
226                     x => avec.push(x.clone())
227                 }
228             }
229             i.attrs = avec;
230             self.fold_item_recur(i)
231         }
232     }
233     let mut cleaner = CommentCleaner;
234     let krate = cleaner.fold_crate(krate);
235     (krate, None)
236 }
237
238 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
239     struct Collapser;
240     impl fold::DocFolder for Collapser {
241         fn fold_item(&mut self, i: Item) -> Option<Item> {
242             let mut docstr = ~"";
243             let mut i = i;
244             for attr in i.attrs.iter() {
245                 match *attr {
246                     clean::NameValue(ref x, ref s) if "doc" == *x => {
247                         docstr.push_str(s.clone());
248                         docstr.push_char('\n');
249                     },
250                     _ => ()
251                 }
252             }
253             let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
254                 &clean::NameValue(ref x, _) if "doc" == *x => false,
255                 _ => true
256             }).map(|x| x.clone()).collect();
257             if "" != docstr {
258                 a.push(clean::NameValue(~"doc", docstr));
259             }
260             i.attrs = a;
261             self.fold_item_recur(i)
262         }
263     }
264     let mut collapser = Collapser;
265     let krate = collapser.fold_crate(krate);
266     (krate, None)
267 }
268
269 pub fn unindent(s: &str) -> ~str {
270     let lines = s.lines_any().collect::<Vec<&str> >();
271     let mut saw_first_line = false;
272     let mut saw_second_line = false;
273     let min_indent = lines.iter().fold(uint::MAX, |min_indent, line| {
274
275         // After we see the first non-whitespace line, look at
276         // the line we have. If it is not whitespace, and therefore
277         // part of the first paragraph, then ignore the indentation
278         // level of the first line
279         let ignore_previous_indents =
280             saw_first_line &&
281             !saw_second_line &&
282             !line.is_whitespace();
283
284         let min_indent = if ignore_previous_indents {
285             uint::MAX
286         } else {
287             min_indent
288         };
289
290         if saw_first_line {
291             saw_second_line = true;
292         }
293
294         if line.is_whitespace() {
295             min_indent
296         } else {
297             saw_first_line = true;
298             let mut spaces = 0;
299             line.chars().all(|char| {
300                 // Only comparing against space because I wouldn't
301                 // know what to do with mixed whitespace chars
302                 if char == ' ' {
303                     spaces += 1;
304                     true
305                 } else {
306                     false
307                 }
308             });
309             cmp::min(min_indent, spaces)
310         }
311     });
312
313     if lines.len() >= 1 {
314         let mut unindented = vec!( lines.get(0).trim() );
315         unindented.push_all(lines.tail().map(|&line| {
316             if line.is_whitespace() {
317                 line
318             } else {
319                 assert!(line.len() >= min_indent);
320                 line.slice_from(min_indent)
321             }
322         }));
323         unindented.connect("\n")
324     } else {
325         s.to_owned()
326     }
327 }
328
329 #[cfg(test)]
330 mod unindent_tests {
331     use super::unindent;
332
333     #[test]
334     fn should_unindent() {
335         let s = ~"    line1\n    line2";
336         let r = unindent(s);
337         assert_eq!(r, ~"line1\nline2");
338     }
339
340     #[test]
341     fn should_unindent_multiple_paragraphs() {
342         let s = ~"    line1\n\n    line2";
343         let r = unindent(s);
344         assert_eq!(r, ~"line1\n\nline2");
345     }
346
347     #[test]
348     fn should_leave_multiple_indent_levels() {
349         // Line 2 is indented another level beyond the
350         // base indentation and should be preserved
351         let s = ~"    line1\n\n        line2";
352         let r = unindent(s);
353         assert_eq!(r, ~"line1\n\n    line2");
354     }
355
356     #[test]
357     fn should_ignore_first_line_indent() {
358         // Thi first line of the first paragraph may not be indented as
359         // far due to the way the doc string was written:
360         //
361         // #[doc = "Start way over here
362         //          and continue here"]
363         let s = ~"line1\n    line2";
364         let r = unindent(s);
365         assert_eq!(r, ~"line1\nline2");
366     }
367
368     #[test]
369     fn should_not_ignore_first_line_indent_in_a_single_line_para() {
370         let s = ~"line1\n\n    line2";
371         let r = unindent(s);
372         assert_eq!(r, ~"line1\n\n    line2");
373     }
374 }