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