]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes.rs
rustdoc: Fix a few stripping issues
[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 rustc::hir::def_id::DefId;
12 use rustc::middle::privacy::AccessLevels;
13 use rustc::util::nodemap::DefIdSet;
14 use std::cmp;
15 use std::mem;
16 use std::string::String;
17 use std::usize;
18
19 use clean::{self, Attributes, GetDefId};
20 use clean::Item;
21 use plugins;
22 use fold;
23 use fold::DocFolder;
24 use fold::FoldItem::Strip;
25
26 /// Strip items marked `#[doc(hidden)]`
27 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
28     let mut retained = DefIdSet();
29
30     // strip all #[doc(hidden)] items
31     let krate = {
32         struct Stripper<'a> {
33             retained: &'a mut DefIdSet,
34             update_retained: bool,
35         }
36         impl<'a> fold::DocFolder for Stripper<'a> {
37             fn fold_item(&mut self, i: Item) -> Option<Item> {
38                 if i.attrs.list("doc").has_word("hidden") {
39                     debug!("found one in strip_hidden; removing");
40                     // use a dedicated hidden item for given item type if any
41                     match i.inner {
42                         clean::StructFieldItem(..) | clean::ModuleItem(..) => {
43                             // We need to recurse into stripped modules to
44                             // strip things like impl methods but when doing so
45                             // we must not add any items to the `retained` set.
46                             let old = mem::replace(&mut self.update_retained, false);
47                             let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
48                             self.update_retained = old;
49                             return ret;
50                         }
51                         _ => return None,
52                     }
53                 } else {
54                     if self.update_retained {
55                         self.retained.insert(i.def_id);
56                     }
57                 }
58                 self.fold_item_recur(i)
59             }
60         }
61         let mut stripper = Stripper{ retained: &mut retained, update_retained: true };
62         stripper.fold_crate(krate)
63     };
64
65     // strip all impls referencing stripped items
66     let mut stripper = ImplStripper { retained: &retained };
67     stripper.fold_crate(krate)
68 }
69
70 /// Strip private items from the point of view of a crate or externally from a
71 /// crate, specified by the `xcrate` flag.
72 pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
73     // This stripper collects all *retained* nodes.
74     let mut retained = DefIdSet();
75     let access_levels = krate.access_levels.clone();
76
77     // strip all private items
78     {
79         let mut stripper = Stripper {
80             retained: &mut retained,
81             access_levels: &access_levels,
82             update_retained: true,
83         };
84         krate = ImportStripper.fold_crate(stripper.fold_crate(krate));
85     }
86
87     // strip all impls referencing private items
88     let mut stripper = ImplStripper { retained: &retained };
89     stripper.fold_crate(krate)
90 }
91
92 struct Stripper<'a> {
93     retained: &'a mut DefIdSet,
94     access_levels: &'a AccessLevels<DefId>,
95     update_retained: bool,
96 }
97
98 impl<'a> fold::DocFolder for Stripper<'a> {
99     fn fold_item(&mut self, i: Item) -> Option<Item> {
100         match i.inner {
101             clean::StrippedItem(..) => {
102                 // We need to recurse into stripped modules to strip things
103                 // like impl methods but when doing so we must not add any
104                 // items to the `retained` set.
105                 let old = mem::replace(&mut self.update_retained, false);
106                 let ret = self.fold_item_recur(i);
107                 self.update_retained = old;
108                 return ret;
109             }
110             // These items can all get re-exported
111             clean::TypedefItem(..) | clean::StaticItem(..) |
112             clean::StructItem(..) | clean::EnumItem(..) |
113             clean::TraitItem(..) | clean::FunctionItem(..) |
114             clean::VariantItem(..) | clean::MethodItem(..) |
115             clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) |
116             clean::ConstantItem(..) => {
117                 if i.def_id.is_local() {
118                     if !self.access_levels.is_exported(i.def_id) {
119                         return None;
120                     }
121                 }
122             }
123
124             clean::StructFieldItem(..) => {
125                 if i.visibility != Some(clean::Public) {
126                     return Strip(i).fold();
127                 }
128             }
129
130             clean::ModuleItem(..) => {
131                 if i.def_id.is_local() && i.visibility != Some(clean::Public) {
132                     let old = mem::replace(&mut self.update_retained, false);
133                     let ret = Strip(self.fold_item_recur(i).unwrap()).fold();
134                     self.update_retained = old;
135                     return ret;
136                 }
137             }
138
139             // handled in the `strip-priv-imports` pass
140             clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
141
142             clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
143
144             // tymethods/macros have no control over privacy
145             clean::MacroItem(..) | clean::TyMethodItem(..) => {}
146
147             // Primitives are never stripped
148             clean::PrimitiveItem(..) => {}
149
150             // Associated consts and types are never stripped
151             clean::AssociatedConstItem(..) |
152             clean::AssociatedTypeItem(..) => {}
153         }
154
155         let fastreturn = match i.inner {
156             // nothing left to do for traits (don't want to filter their
157             // methods out, visibility controlled by the trait)
158             clean::TraitItem(..) => true,
159
160             // implementations of traits are always public.
161             clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
162             // Struct variant fields have inherited visibility
163             clean::VariantItem(clean::Variant {
164                 kind: clean::StructVariant(..)
165             }) => true,
166             _ => false,
167         };
168
169         let i = if fastreturn {
170             if self.update_retained {
171                 self.retained.insert(i.def_id);
172             }
173             return Some(i);
174         } else {
175             self.fold_item_recur(i)
176         };
177
178         i.and_then(|i| {
179             match i.inner {
180                 // emptied modules have no need to exist
181                 clean::ModuleItem(ref m)
182                     if m.items.is_empty() &&
183                        i.doc_value().is_none() => None,
184                 _ => {
185                     if self.update_retained {
186                         self.retained.insert(i.def_id);
187                     }
188                     Some(i)
189                 }
190             }
191         })
192     }
193 }
194
195 // This stripper discards all impls which reference stripped items
196 struct ImplStripper<'a> {
197     retained: &'a DefIdSet
198 }
199
200 impl<'a> fold::DocFolder for ImplStripper<'a> {
201     fn fold_item(&mut self, i: Item) -> Option<Item> {
202         if let clean::ImplItem(ref imp) = i.inner {
203             // emptied none trait impls can be stripped
204             if imp.trait_.is_none() && imp.items.is_empty() {
205                 return None;
206             }
207             if let Some(did) = imp.for_.def_id() {
208                 if did.is_local() && !imp.for_.is_generic() &&
209                     !self.retained.contains(&did)
210                 {
211                     return None;
212                 }
213             }
214             if let Some(did) = imp.trait_.def_id() {
215                 if did.is_local() && !self.retained.contains(&did) {
216                     return None;
217                 }
218             }
219         }
220         self.fold_item_recur(i)
221     }
222 }
223
224 // This stripper discards all private import statements (`use`, `extern crate`)
225 struct ImportStripper;
226 impl fold::DocFolder for ImportStripper {
227     fn fold_item(&mut self, i: Item) -> Option<Item> {
228         match i.inner {
229             clean::ExternCrateItem(..) |
230             clean::ImportItem(..) if i.visibility != Some(clean::Public) => None,
231             _ => self.fold_item_recur(i)
232         }
233     }
234 }
235
236 pub fn strip_priv_imports(krate: clean::Crate)  -> plugins::PluginResult {
237     ImportStripper.fold_crate(krate)
238 }
239
240 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
241     struct CommentCleaner;
242     impl fold::DocFolder for CommentCleaner {
243         fn fold_item(&mut self, mut i: Item) -> Option<Item> {
244             let mut avec: Vec<clean::Attribute> = Vec::new();
245             for attr in &i.attrs {
246                 match attr {
247                     &clean::NameValue(ref x, ref s)
248                             if "doc" == *x => {
249                         avec.push(clean::NameValue("doc".to_string(),
250                                                    unindent(s)))
251                     }
252                     x => avec.push(x.clone())
253                 }
254             }
255             i.attrs = avec;
256             self.fold_item_recur(i)
257         }
258     }
259     let mut cleaner = CommentCleaner;
260     let krate = cleaner.fold_crate(krate);
261     krate
262 }
263
264 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
265     struct Collapser;
266     impl fold::DocFolder for Collapser {
267         fn fold_item(&mut self, mut i: Item) -> Option<Item> {
268             let mut docstr = String::new();
269             for attr in &i.attrs {
270                 if let clean::NameValue(ref x, ref s) = *attr {
271                     if "doc" == *x {
272                         docstr.push_str(s);
273                         docstr.push('\n');
274                     }
275                 }
276             }
277             let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
278                 &clean::NameValue(ref x, _) if "doc" == *x => false,
279                 _ => true
280             }).cloned().collect();
281             if !docstr.is_empty() {
282                 a.push(clean::NameValue("doc".to_string(), docstr));
283             }
284             i.attrs = a;
285             self.fold_item_recur(i)
286         }
287     }
288     let mut collapser = Collapser;
289     let krate = collapser.fold_crate(krate);
290     krate
291 }
292
293 pub fn unindent(s: &str) -> String {
294     let lines = s.lines().collect::<Vec<&str> >();
295     let mut saw_first_line = false;
296     let mut saw_second_line = false;
297     let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
298
299         // After we see the first non-whitespace line, look at
300         // the line we have. If it is not whitespace, and therefore
301         // part of the first paragraph, then ignore the indentation
302         // level of the first line
303         let ignore_previous_indents =
304             saw_first_line &&
305             !saw_second_line &&
306             !line.chars().all(|c| c.is_whitespace());
307
308         let min_indent = if ignore_previous_indents {
309             usize::MAX
310         } else {
311             min_indent
312         };
313
314         if saw_first_line {
315             saw_second_line = true;
316         }
317
318         if line.chars().all(|c| c.is_whitespace()) {
319             min_indent
320         } else {
321             saw_first_line = true;
322             let mut whitespace = 0;
323             line.chars().all(|char| {
324                 // Compare against either space or tab, ignoring whether they
325                 // are mixed or not
326                 if char == ' ' || char == '\t' {
327                     whitespace += 1;
328                     true
329                 } else {
330                     false
331                 }
332             });
333             cmp::min(min_indent, whitespace)
334         }
335     });
336
337     if !lines.is_empty() {
338         let mut unindented = vec![ lines[0].trim().to_string() ];
339         unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
340             if line.chars().all(|c| c.is_whitespace()) {
341                 line.to_string()
342             } else {
343                 assert!(line.len() >= min_indent);
344                 line[min_indent..].to_string()
345             }
346         }).collect::<Vec<_>>());
347         unindented.join("\n")
348     } else {
349         s.to_string()
350     }
351 }
352
353 #[cfg(test)]
354 mod unindent_tests {
355     use super::unindent;
356
357     #[test]
358     fn should_unindent() {
359         let s = "    line1\n    line2".to_string();
360         let r = unindent(&s);
361         assert_eq!(r, "line1\nline2");
362     }
363
364     #[test]
365     fn should_unindent_multiple_paragraphs() {
366         let s = "    line1\n\n    line2".to_string();
367         let r = unindent(&s);
368         assert_eq!(r, "line1\n\nline2");
369     }
370
371     #[test]
372     fn should_leave_multiple_indent_levels() {
373         // Line 2 is indented another level beyond the
374         // base indentation and should be preserved
375         let s = "    line1\n\n        line2".to_string();
376         let r = unindent(&s);
377         assert_eq!(r, "line1\n\n    line2");
378     }
379
380     #[test]
381     fn should_ignore_first_line_indent() {
382         // The first line of the first paragraph may not be indented as
383         // far due to the way the doc string was written:
384         //
385         // #[doc = "Start way over here
386         //          and continue here"]
387         let s = "line1\n    line2".to_string();
388         let r = unindent(&s);
389         assert_eq!(r, "line1\nline2");
390     }
391
392     #[test]
393     fn should_not_ignore_first_line_indent_in_a_single_line_para() {
394         let s = "line1\n\n    line2".to_string();
395         let r = unindent(&s);
396         assert_eq!(r, "line1\n\n    line2");
397     }
398
399     #[test]
400     fn should_unindent_tabs() {
401         let s = "\tline1\n\tline2".to_string();
402         let r = unindent(&s);
403         assert_eq!(r, "line1\nline2");
404     }
405
406     #[test]
407     fn should_trim_mixed_indentation() {
408         let s = "\t    line1\n\t    line2".to_string();
409         let r = unindent(&s);
410         assert_eq!(r, "line1\nline2");
411
412         let s = "    \tline1\n    \tline2".to_string();
413         let r = unindent(&s);
414         assert_eq!(r, "line1\nline2");
415     }
416 }