]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes.rs
7c4d28d5adb6bab566f09fb78dd203b72dddd754
[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::collections::HashSet;
12 use rustc::util::nodemap::NodeSet;
13 use std::cmp;
14 use std::string::String;
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                         ref trait_, ..
72                     }) => {
73                         // Impls for stripped types don't need to exist
74                         if self.stripped.contains(&did.node) {
75                             return None;
76                         }
77                         // Impls of stripped traits also don't need to exist
78                         match *trait_ {
79                             Some(clean::ResolvedPath { did, .. }) => {
80                                 if self.stripped.contains(&did.node) {
81                                     return None
82                                 }
83                             }
84                             _ => {}
85                         }
86                     }
87                     _ => {}
88                 }
89                 self.fold_item_recur(i)
90             }
91         }
92         let mut stripper = ImplStripper{ stripped: &mut stripped };
93         stripper.fold_crate(krate)
94     };
95
96     (krate, None)
97 }
98
99 /// Strip private items from the point of view of a crate or externally from a
100 /// crate, specified by the `xcrate` flag.
101 pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
102     // This stripper collects all *retained* nodes.
103     let mut retained = HashSet::new();
104     let analysis = super::analysiskey.get().unwrap();
105     let exported_items = analysis.exported_items.clone();
106
107     // strip all private items
108     {
109         let mut stripper = Stripper {
110             retained: &mut retained,
111             exported_items: &exported_items,
112         };
113         krate = stripper.fold_crate(krate);
114     }
115
116     // strip all private implementations of traits
117     {
118         let mut stripper = ImplStripper(&retained);
119         krate = stripper.fold_crate(krate);
120     }
121     (krate, None)
122 }
123
124 struct Stripper<'a> {
125     retained: &'a mut HashSet<ast::NodeId>,
126     exported_items: &'a NodeSet,
127 }
128
129 impl<'a> fold::DocFolder for Stripper<'a> {
130     fn fold_item(&mut self, i: Item) -> Option<Item> {
131         match i.inner {
132             // These items can all get re-exported
133             clean::TypedefItem(..) | clean::StaticItem(..) |
134             clean::StructItem(..) | clean::EnumItem(..) |
135             clean::TraitItem(..) | clean::FunctionItem(..) |
136             clean::VariantItem(..) | clean::MethodItem(..) |
137             clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
138                 if ast_util::is_local(i.def_id) {
139                     if !self.exported_items.contains(&i.def_id.node) {
140                         return None;
141                     }
142                     // Traits are in exported_items even when they're totally private.
143                     if i.is_trait() && i.visibility != Some(ast::Public) {
144                         return None;
145                     }
146                 }
147             }
148
149             clean::ConstantItem(..) => {
150                 if ast_util::is_local(i.def_id) &&
151                    !self.exported_items.contains(&i.def_id.node) {
152                     return None;
153                 }
154             }
155
156             clean::ViewItemItem(..) => {
157                 if i.visibility != Some(ast::Public) {
158                     return None
159                 }
160             }
161
162             clean::StructFieldItem(..) => {
163                 if i.visibility != Some(ast::Public) {
164                     return Some(clean::Item {
165                         inner: clean::StructFieldItem(clean::HiddenStructField),
166                         ..i
167                     })
168                 }
169             }
170
171             // handled below
172             clean::ModuleItem(..) => {}
173
174             // trait impls for private items should be stripped
175             clean::ImplItem(clean::Impl{
176                 for_: clean::ResolvedPath{ did, .. }, ..
177             }) => {
178                 if ast_util::is_local(did) &&
179                    !self.exported_items.contains(&did.node) {
180                     return None;
181                 }
182             }
183             clean::ImplItem(..) => {}
184
185             // tymethods/macros have no control over privacy
186             clean::MacroItem(..) | clean::TyMethodItem(..) => {}
187
188             // Primitives are never stripped
189             clean::PrimitiveItem(..) => {}
190
191             // Associated types are never stripped
192             clean::AssociatedTypeItem(..) => {}
193         }
194
195         let fastreturn = match i.inner {
196             // nothing left to do for traits (don't want to filter their
197             // methods out, visibility controlled by the trait)
198             clean::TraitItem(..) => true,
199
200             // implementations of traits are always public.
201             clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
202
203             _ => false,
204         };
205
206         let i = if fastreturn {
207             self.retained.insert(i.def_id.node);
208             return Some(i);
209         } else {
210             self.fold_item_recur(i)
211         };
212
213         match i {
214             Some(i) => {
215                 match i.inner {
216                     // emptied modules/impls have no need to exist
217                     clean::ModuleItem(ref m)
218                         if m.items.len() == 0 &&
219                            i.doc_value().is_none() => None,
220                     clean::ImplItem(ref i) if i.items.len() == 0 => None,
221                     _ => {
222                         self.retained.insert(i.def_id.node);
223                         Some(i)
224                     }
225                 }
226             }
227             None => None,
228         }
229     }
230 }
231
232 // This stripper discards all private impls of traits
233 struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
234 impl<'a> fold::DocFolder for ImplStripper<'a> {
235     fn fold_item(&mut self, i: Item) -> Option<Item> {
236         match i.inner {
237             clean::ImplItem(ref imp) => {
238                 match imp.trait_ {
239                     Some(clean::ResolvedPath{ did, .. }) => {
240                         let ImplStripper(s) = *self;
241                         if ast_util::is_local(did) && !s.contains(&did.node) {
242                             return None;
243                         }
244                     }
245                     Some(..) | None => {}
246                 }
247             }
248             _ => {}
249         }
250         self.fold_item_recur(i)
251     }
252 }
253
254
255 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
256     struct CommentCleaner;
257     impl fold::DocFolder for CommentCleaner {
258         fn fold_item(&mut self, i: Item) -> Option<Item> {
259             let mut i = i;
260             let mut avec: Vec<clean::Attribute> = Vec::new();
261             for attr in i.attrs.iter() {
262                 match attr {
263                     &clean::NameValue(ref x, ref s)
264                             if "doc" == x.as_slice() => {
265                         avec.push(clean::NameValue("doc".to_string(),
266                                                    unindent(s.as_slice())))
267                     }
268                     x => avec.push(x.clone())
269                 }
270             }
271             i.attrs = avec;
272             self.fold_item_recur(i)
273         }
274     }
275     let mut cleaner = CommentCleaner;
276     let krate = cleaner.fold_crate(krate);
277     (krate, None)
278 }
279
280 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
281     struct Collapser;
282     impl fold::DocFolder for Collapser {
283         fn fold_item(&mut self, i: Item) -> Option<Item> {
284             let mut docstr = String::new();
285             let mut i = i;
286             for attr in i.attrs.iter() {
287                 match *attr {
288                     clean::NameValue(ref x, ref s)
289                             if "doc" == x.as_slice() => {
290                         docstr.push_str(s.as_slice());
291                         docstr.push('\n');
292                     },
293                     _ => ()
294                 }
295             }
296             let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
297                 &clean::NameValue(ref x, _) if "doc" == x.as_slice() => false,
298                 _ => true
299             }).map(|x| x.clone()).collect();
300             if docstr.len() > 0 {
301                 a.push(clean::NameValue("doc".to_string(), docstr));
302             }
303             i.attrs = a;
304             self.fold_item_recur(i)
305         }
306     }
307     let mut collapser = Collapser;
308     let krate = collapser.fold_crate(krate);
309     (krate, None)
310 }
311
312 pub fn unindent(s: &str) -> String {
313     let lines = s.lines_any().collect::<Vec<&str> >();
314     let mut saw_first_line = false;
315     let mut saw_second_line = false;
316     let min_indent = lines.iter().fold(uint::MAX, |min_indent, line| {
317
318         // After we see the first non-whitespace line, look at
319         // the line we have. If it is not whitespace, and therefore
320         // part of the first paragraph, then ignore the indentation
321         // level of the first line
322         let ignore_previous_indents =
323             saw_first_line &&
324             !saw_second_line &&
325             !line.is_whitespace();
326
327         let min_indent = if ignore_previous_indents {
328             uint::MAX
329         } else {
330             min_indent
331         };
332
333         if saw_first_line {
334             saw_second_line = true;
335         }
336
337         if line.is_whitespace() {
338             min_indent
339         } else {
340             saw_first_line = true;
341             let mut spaces = 0;
342             line.chars().all(|char| {
343                 // Only comparing against space because I wouldn't
344                 // know what to do with mixed whitespace chars
345                 if char == ' ' {
346                     spaces += 1;
347                     true
348                 } else {
349                     false
350                 }
351             });
352             cmp::min(min_indent, spaces)
353         }
354     });
355
356     if lines.len() >= 1 {
357         let mut unindented = vec![ lines[0].trim().to_string() ];
358         unindented.push_all(lines.tail().iter().map(|&line| {
359             if line.is_whitespace() {
360                 line.to_string()
361             } else {
362                 assert!(line.len() >= min_indent);
363                 line.slice_from(min_indent).to_string()
364             }
365         }).collect::<Vec<_>>().as_slice());
366         unindented.connect("\n")
367     } else {
368         s.to_string()
369     }
370 }
371
372 #[cfg(test)]
373 mod unindent_tests {
374     use super::unindent;
375
376     #[test]
377     fn should_unindent() {
378         let s = "    line1\n    line2".to_string();
379         let r = unindent(s.as_slice());
380         assert_eq!(r.as_slice(), "line1\nline2");
381     }
382
383     #[test]
384     fn should_unindent_multiple_paragraphs() {
385         let s = "    line1\n\n    line2".to_string();
386         let r = unindent(s.as_slice());
387         assert_eq!(r.as_slice(), "line1\n\nline2");
388     }
389
390     #[test]
391     fn should_leave_multiple_indent_levels() {
392         // Line 2 is indented another level beyond the
393         // base indentation and should be preserved
394         let s = "    line1\n\n        line2".to_string();
395         let r = unindent(s.as_slice());
396         assert_eq!(r.as_slice(), "line1\n\n    line2");
397     }
398
399     #[test]
400     fn should_ignore_first_line_indent() {
401         // The first line of the first paragraph may not be indented as
402         // far due to the way the doc string was written:
403         //
404         // #[doc = "Start way over here
405         //          and continue here"]
406         let s = "line1\n    line2".to_string();
407         let r = unindent(s.as_slice());
408         assert_eq!(r.as_slice(), "line1\nline2");
409     }
410
411     #[test]
412     fn should_not_ignore_first_line_indent_in_a_single_line_para() {
413         let s = "line1\n\n    line2".to_string();
414         let r = unindent(s.as_slice());
415         assert_eq!(r.as_slice(), "line1\n\n    line2");
416     }
417 }