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