]> git.lizzy.rs Git - rust.git/blob - src/librustdoc/passes.rs
Split out rustdoc pass to strip private imports
[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::middle::def_id::DefId;
12 use rustc::middle::privacy::AccessLevels;
13 use rustc::util::nodemap::DefIdSet;
14 use std::cmp;
15 use std::string::String;
16 use std::usize;
17 use rustc_front::hir;
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 = DefIdSet();
28
29     // strip all #[doc(hidden)] items
30     let krate = {
31         struct Stripper<'a> {
32             stripped: &'a mut DefIdSet
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);
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 DefIdSet
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) {
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) {
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 = DefIdSet();
98     let analysis = super::ANALYSISKEY.with(|a| a.clone());
99     let analysis = analysis.borrow();
100     let analysis = analysis.as_ref().unwrap();
101     let access_levels = analysis.access_levels.clone();
102
103     // strip all private items
104     {
105         let mut stripper = Stripper {
106             retained: &mut retained,
107             access_levels: &access_levels,
108         };
109         krate = ImportStripper.fold_crate(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 DefIdSet,
122     access_levels: &'a AccessLevels<DefId>,
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 i.def_id.is_local() {
135                     if !self.access_levels.is_exported(i.def_id) {
136                         return None;
137                     }
138                 }
139             }
140
141             clean::ConstantItem(..) => {
142                 if i.def_id.is_local() && !self.access_levels.is_exported(i.def_id) {
143                     return None;
144                 }
145             }
146
147             clean::StructFieldItem(..) => {
148                 if i.visibility != Some(hir::Public) {
149                     return Some(clean::Item {
150                         inner: clean::StructFieldItem(clean::HiddenStructField),
151                         ..i
152                     })
153                 }
154             }
155
156             // handled below
157             clean::ModuleItem(..) => {}
158
159             // trait impls for private items should be stripped
160             clean::ImplItem(clean::Impl{
161                 for_: clean::ResolvedPath{ did, .. }, ..
162             }) => {
163                 if did.is_local() && !self.access_levels.is_exported(did) {
164                     return None;
165                 }
166             }
167             // handled in the `strip-priv-imports` pass
168             clean::ExternCrateItem(..) | clean::ImportItem(_) => {}
169
170             clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
171
172             // tymethods/macros have no control over privacy
173             clean::MacroItem(..) | clean::TyMethodItem(..) => {}
174
175             // Primitives are never stripped
176             clean::PrimitiveItem(..) => {}
177
178             // Associated consts and types are never stripped
179             clean::AssociatedConstItem(..) |
180             clean::AssociatedTypeItem(..) => {}
181         }
182
183         let fastreturn = match i.inner {
184             // nothing left to do for traits (don't want to filter their
185             // methods out, visibility controlled by the trait)
186             clean::TraitItem(..) => true,
187
188             // implementations of traits are always public.
189             clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
190
191             // Struct variant fields have inherited visibility
192             clean::VariantItem(clean::Variant {
193                 kind: clean::StructVariant(..)
194             }) => true,
195             _ => false,
196         };
197
198         let i = if fastreturn {
199             self.retained.insert(i.def_id);
200             return Some(i);
201         } else {
202             self.fold_item_recur(i)
203         };
204
205         match i {
206             Some(i) => {
207                 match i.inner {
208                     // emptied modules/impls have no need to exist
209                     clean::ModuleItem(ref m)
210                         if m.items.is_empty() &&
211                            i.doc_value().is_none() => None,
212                     clean::ImplItem(ref i) if i.items.is_empty() => None,
213                     _ => {
214                         self.retained.insert(i.def_id);
215                         Some(i)
216                     }
217                 }
218             }
219             None => None,
220         }
221     }
222 }
223
224 // This stripper discards all private impls of traits
225 struct ImplStripper<'a>(&'a DefIdSet);
226 impl<'a> fold::DocFolder for ImplStripper<'a> {
227     fn fold_item(&mut self, i: Item) -> Option<Item> {
228         if let clean::ImplItem(ref imp) = i.inner {
229             match imp.trait_ {
230                 Some(clean::ResolvedPath{ did, .. }) => {
231                     if did.is_local() && !self.0.contains(&did) {
232                         return None;
233                     }
234                 }
235                 Some(..) | None => {}
236             }
237         }
238         self.fold_item_recur(i)
239     }
240 }
241
242 // This stripper discards all private import statements (`use`, `extern crate`)
243 struct ImportStripper;
244 impl fold::DocFolder for ImportStripper {
245     fn fold_item(&mut self, i: Item) -> Option<Item> {
246         match i.inner {
247             clean::ExternCrateItem(..) |
248             clean::ImportItem(..) if i.visibility != Some(hir::Public) => None,
249             _ => self.fold_item_recur(i)
250         }
251     }
252 }
253
254 pub fn strip_priv_imports(krate: clean::Crate)  -> plugins::PluginResult {
255     (ImportStripper.fold_crate(krate), None)
256 }
257
258 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
259     struct CommentCleaner;
260     impl fold::DocFolder for CommentCleaner {
261         fn fold_item(&mut self, i: Item) -> Option<Item> {
262             let mut i = i;
263             let mut avec: Vec<clean::Attribute> = Vec::new();
264             for attr in &i.attrs {
265                 match attr {
266                     &clean::NameValue(ref x, ref s)
267                             if "doc" == *x => {
268                         avec.push(clean::NameValue("doc".to_string(),
269                                                    unindent(s)))
270                     }
271                     x => avec.push(x.clone())
272                 }
273             }
274             i.attrs = avec;
275             self.fold_item_recur(i)
276         }
277     }
278     let mut cleaner = CommentCleaner;
279     let krate = cleaner.fold_crate(krate);
280     (krate, None)
281 }
282
283 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
284     struct Collapser;
285     impl fold::DocFolder for Collapser {
286         fn fold_item(&mut self, i: Item) -> Option<Item> {
287             let mut docstr = String::new();
288             let mut i = i;
289             for attr in &i.attrs {
290                 match *attr {
291                     clean::NameValue(ref x, ref s)
292                             if "doc" == *x => {
293                         docstr.push_str(s);
294                         docstr.push('\n');
295                     },
296                     _ => ()
297                 }
298             }
299             let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
300                 &clean::NameValue(ref x, _) if "doc" == *x => false,
301                 _ => true
302             }).cloned().collect();
303             if !docstr.is_empty() {
304                 a.push(clean::NameValue("doc".to_string(), docstr));
305             }
306             i.attrs = a;
307             self.fold_item_recur(i)
308         }
309     }
310     let mut collapser = Collapser;
311     let krate = collapser.fold_crate(krate);
312     (krate, None)
313 }
314
315 pub fn unindent(s: &str) -> String {
316     let lines = s.lines().collect::<Vec<&str> >();
317     let mut saw_first_line = false;
318     let mut saw_second_line = false;
319     let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
320
321         // After we see the first non-whitespace line, look at
322         // the line we have. If it is not whitespace, and therefore
323         // part of the first paragraph, then ignore the indentation
324         // level of the first line
325         let ignore_previous_indents =
326             saw_first_line &&
327             !saw_second_line &&
328             !line.chars().all(|c| c.is_whitespace());
329
330         let min_indent = if ignore_previous_indents {
331             usize::MAX
332         } else {
333             min_indent
334         };
335
336         if saw_first_line {
337             saw_second_line = true;
338         }
339
340         if line.chars().all(|c| c.is_whitespace()) {
341             min_indent
342         } else {
343             saw_first_line = true;
344             let mut whitespace = 0;
345             line.chars().all(|char| {
346                 // Compare against either space or tab, ignoring whether they
347                 // are mixed or not
348                 if char == ' ' || char == '\t' {
349                     whitespace += 1;
350                     true
351                 } else {
352                     false
353                 }
354             });
355             cmp::min(min_indent, whitespace)
356         }
357     });
358
359     if !lines.is_empty() {
360         let mut unindented = vec![ lines[0].trim().to_string() ];
361         unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
362             if line.chars().all(|c| c.is_whitespace()) {
363                 line.to_string()
364             } else {
365                 assert!(line.len() >= min_indent);
366                 line[min_indent..].to_string()
367             }
368         }).collect::<Vec<_>>());
369         unindented.join("\n")
370     } else {
371         s.to_string()
372     }
373 }
374
375 #[cfg(test)]
376 mod unindent_tests {
377     use super::unindent;
378
379     #[test]
380     fn should_unindent() {
381         let s = "    line1\n    line2".to_string();
382         let r = unindent(&s);
383         assert_eq!(r, "line1\nline2");
384     }
385
386     #[test]
387     fn should_unindent_multiple_paragraphs() {
388         let s = "    line1\n\n    line2".to_string();
389         let r = unindent(&s);
390         assert_eq!(r, "line1\n\nline2");
391     }
392
393     #[test]
394     fn should_leave_multiple_indent_levels() {
395         // Line 2 is indented another level beyond the
396         // base indentation and should be preserved
397         let s = "    line1\n\n        line2".to_string();
398         let r = unindent(&s);
399         assert_eq!(r, "line1\n\n    line2");
400     }
401
402     #[test]
403     fn should_ignore_first_line_indent() {
404         // The first line of the first paragraph may not be indented as
405         // far due to the way the doc string was written:
406         //
407         // #[doc = "Start way over here
408         //          and continue here"]
409         let s = "line1\n    line2".to_string();
410         let r = unindent(&s);
411         assert_eq!(r, "line1\nline2");
412     }
413
414     #[test]
415     fn should_not_ignore_first_line_indent_in_a_single_line_para() {
416         let s = "line1\n\n    line2".to_string();
417         let r = unindent(&s);
418         assert_eq!(r, "line1\n\n    line2");
419     }
420
421     #[test]
422     fn should_unindent_tabs() {
423         let s = "\tline1\n\tline2".to_string();
424         let r = unindent(&s);
425         assert_eq!(r, "line1\nline2");
426     }
427
428     #[test]
429     fn should_trim_mixed_indentation() {
430         let s = "\t    line1\n\t    line2".to_string();
431         let r = unindent(&s);
432         assert_eq!(r, "line1\nline2");
433
434         let s = "    \tline1\n    \tline2".to_string();
435         let r = unindent(&s);
436         assert_eq!(r, "line1\nline2");
437     }
438 }