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.
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.
11 use rustc::hir::def_id::DefId;
12 use rustc::middle::privacy::AccessLevels;
13 use rustc::util::nodemap::DefIdSet;
16 use std::string::String;
19 use clean::{self, Attributes, GetDefId};
24 use fold::FoldItem::Strip;
26 /// Strip items marked `#[doc(hidden)]`
27 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
28 let mut retained = DefIdSet();
30 // strip all #[doc(hidden)] items
33 retained: &'a mut DefIdSet,
34 update_retained: bool,
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
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;
54 if self.update_retained {
55 self.retained.insert(i.def_id);
58 self.fold_item_recur(i)
61 let mut stripper = Stripper{ retained: &mut retained, update_retained: true };
62 stripper.fold_crate(krate)
65 // strip all impls referencing stripped items
66 let mut stripper = ImplStripper { retained: &retained };
67 stripper.fold_crate(krate)
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();
77 // strip all private items
79 let mut stripper = Stripper {
80 retained: &mut retained,
81 access_levels: &access_levels,
82 update_retained: true,
84 krate = ImportStripper.fold_crate(stripper.fold_crate(krate));
87 // strip all impls referencing private items
88 let mut stripper = ImplStripper { retained: &retained };
89 stripper.fold_crate(krate)
93 retained: &'a mut DefIdSet,
94 access_levels: &'a AccessLevels<DefId>,
95 update_retained: bool,
98 impl<'a> fold::DocFolder for Stripper<'a> {
99 fn fold_item(&mut self, i: Item) -> Option<Item> {
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;
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) {
124 clean::StructFieldItem(..) => {
125 if i.visibility != Some(clean::Public) {
126 return Strip(i).fold();
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;
139 // handled in the `strip-priv-imports` pass
140 clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
142 clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
144 // tymethods/macros have no control over privacy
145 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
147 // Primitives are never stripped
148 clean::PrimitiveItem(..) => {}
150 // Associated consts and types are never stripped
151 clean::AssociatedConstItem(..) |
152 clean::AssociatedTypeItem(..) => {}
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,
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(..)
169 let i = if fastreturn {
170 if self.update_retained {
171 self.retained.insert(i.def_id);
175 self.fold_item_recur(i)
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,
185 if self.update_retained {
186 self.retained.insert(i.def_id);
195 // This stripper discards all impls which reference stripped items
196 struct ImplStripper<'a> {
197 retained: &'a DefIdSet
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() {
207 if let Some(did) = imp.for_.def_id() {
208 if did.is_local() && !imp.for_.is_generic() &&
209 !self.retained.contains(&did)
214 if let Some(did) = imp.trait_.def_id() {
215 if did.is_local() && !self.retained.contains(&did) {
220 self.fold_item_recur(i)
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> {
229 clean::ExternCrateItem(..) |
230 clean::ImportItem(..) if i.visibility != Some(clean::Public) => None,
231 _ => self.fold_item_recur(i)
236 pub fn strip_priv_imports(krate: clean::Crate) -> plugins::PluginResult {
237 ImportStripper.fold_crate(krate)
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 {
247 &clean::NameValue(ref x, ref s)
249 avec.push(clean::NameValue("doc".to_string(),
252 x => avec.push(x.clone())
256 self.fold_item_recur(i)
259 let mut cleaner = CommentCleaner;
260 let krate = cleaner.fold_crate(krate);
264 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
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 {
277 let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
278 &clean::NameValue(ref x, _) if "doc" == *x => false,
280 }).cloned().collect();
281 if !docstr.is_empty() {
282 a.push(clean::NameValue("doc".to_string(), docstr));
285 self.fold_item_recur(i)
288 let mut collapser = Collapser;
289 let krate = collapser.fold_crate(krate);
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| {
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 =
306 !line.chars().all(|c| c.is_whitespace());
308 let min_indent = if ignore_previous_indents {
315 saw_second_line = true;
318 if line.chars().all(|c| c.is_whitespace()) {
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
326 if char == ' ' || char == '\t' {
333 cmp::min(min_indent, whitespace)
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()) {
343 assert!(line.len() >= min_indent);
344 line[min_indent..].to_string()
346 }).collect::<Vec<_>>());
347 unindented.join("\n")
358 fn should_unindent() {
359 let s = " line1\n line2".to_string();
360 let r = unindent(&s);
361 assert_eq!(r, "line1\nline2");
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");
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");
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:
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");
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");
400 fn should_unindent_tabs() {
401 let s = "\tline1\n\tline2".to_string();
402 let r = unindent(&s);
403 assert_eq!(r, "line1\nline2");
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");
412 let s = " \tline1\n \tline2".to_string();
413 let r = unindent(&s);
414 assert_eq!(r, "line1\nline2");