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