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 collections::HashSet;
12 use rustc::util::nodemap::NodeSet;
14 use std::string::String;
25 /// Strip items marked `#[doc(hidden)]`
26 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
27 let mut stripped = HashSet::new();
29 // strip all #[doc(hidden)] items
32 stripped: &'a mut HashSet<ast::NodeId>
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);
40 // use a dedicated hidden item for given item type if any
42 clean::StructFieldItem(..) => {
43 return Some(clean::Item {
44 inner: clean::StructFieldItem(clean::HiddenStructField),
54 self.fold_item_recur(i)
57 let mut stripper = Stripper{ stripped: &mut stripped };
58 stripper.fold_crate(krate)
61 // strip any traits implemented on stripped items
63 struct ImplStripper<'a> {
64 stripped: &'a mut HashSet<ast::NodeId>
66 impl<'a> fold::DocFolder for ImplStripper<'a> {
67 fn fold_item(&mut self, i: Item) -> Option<Item> {
69 clean::ImplItem(clean::Impl{
70 for_: clean::ResolvedPath{ did, .. },
73 // Impls for stripped don't need to exist
74 if self.stripped.contains(&did.node) {
77 // Impls of stripped traits also don't need to exist
79 Some(clean::ResolvedPath { did, .. }) => {
80 if self.stripped.contains(&did.node) {
89 self.fold_item_recur(i)
92 let mut stripper = ImplStripper{ stripped: &mut stripped };
93 stripper.fold_crate(krate)
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();
107 // strip all private items
109 let mut stripper = Stripper {
110 retained: &mut retained,
111 exported_items: &exported_items,
113 krate = stripper.fold_crate(krate);
116 // strip all private implementations of traits
118 let mut stripper = ImplStripper(&retained);
119 krate = stripper.fold_crate(krate);
124 struct Stripper<'a> {
125 retained: &'a mut HashSet<ast::NodeId>,
126 exported_items: &'a NodeSet,
129 impl<'a> fold::DocFolder for Stripper<'a> {
130 fn fold_item(&mut self, i: Item) -> Option<Item> {
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 !self.exported_items.contains(&i.def_id.node) {
144 clean::ViewItemItem(..) => {
145 if i.visibility != Some(ast::Public) {
150 clean::StructFieldItem(..) => {
151 if i.visibility != Some(ast::Public) {
152 return Some(clean::Item {
153 inner: clean::StructFieldItem(clean::HiddenStructField),
160 clean::ModuleItem(..) => {}
162 // trait impls for private items should be stripped
163 clean::ImplItem(clean::Impl{
164 for_: clean::ResolvedPath{ did, .. }, ..
166 if ast_util::is_local(did) &&
167 !self.exported_items.contains(&did.node) {
171 clean::ImplItem(..) => {}
173 // tymethods/macros have no control over privacy
174 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
176 // Primitives are never stripped
177 clean::PrimitiveItem(..) => {}
180 let fastreturn = match i.inner {
181 // nothing left to do for traits (don't want to filter their
182 // methods out, visibility controlled by the trait)
183 clean::TraitItem(..) => true,
185 // implementations of traits are always public.
186 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
191 let i = if fastreturn {
192 self.retained.insert(i.def_id.node);
195 self.fold_item_recur(i)
201 // emptied modules/impls have no need to exist
202 clean::ModuleItem(ref m)
203 if m.items.len() == 0 &&
204 i.doc_value().is_none() => None,
205 clean::ImplItem(ref i) if i.methods.len() == 0 => None,
207 self.retained.insert(i.def_id.node);
217 // This stripper discards all private impls of traits
218 struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
219 impl<'a> fold::DocFolder for ImplStripper<'a> {
220 fn fold_item(&mut self, i: Item) -> Option<Item> {
222 clean::ImplItem(ref imp) => {
224 Some(clean::ResolvedPath{ did, .. }) => {
225 let ImplStripper(s) = *self;
226 if ast_util::is_local(did) && !s.contains(&did.node) {
230 Some(..) | None => {}
235 self.fold_item_recur(i)
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, i: Item) -> Option<Item> {
245 let mut avec: Vec<clean::Attribute> = Vec::new();
246 for attr in i.attrs.iter() {
248 &clean::NameValue(ref x, ref s)
249 if "doc" == x.as_slice() => {
250 avec.push(clean::NameValue("doc".to_string(),
251 unindent(s.as_slice())))
253 x => avec.push(x.clone())
257 self.fold_item_recur(i)
260 let mut cleaner = CommentCleaner;
261 let krate = cleaner.fold_crate(krate);
265 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
267 impl fold::DocFolder for Collapser {
268 fn fold_item(&mut self, i: Item) -> Option<Item> {
269 let mut docstr = String::new();
271 for attr in i.attrs.iter() {
273 clean::NameValue(ref x, ref s)
274 if "doc" == x.as_slice() => {
275 docstr.push_str(s.as_slice());
276 docstr.push_char('\n');
281 let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
282 &clean::NameValue(ref x, _) if "doc" == x.as_slice() => false,
284 }).map(|x| x.clone()).collect();
285 if docstr.len() > 0 {
286 a.push(clean::NameValue("doc".to_string(), docstr));
289 self.fold_item_recur(i)
292 let mut collapser = Collapser;
293 let krate = collapser.fold_crate(krate);
297 pub fn unindent(s: &str) -> String {
298 let lines = s.lines_any().collect::<Vec<&str> >();
299 let mut saw_first_line = false;
300 let mut saw_second_line = false;
301 let min_indent = lines.iter().fold(uint::MAX, |min_indent, line| {
303 // After we see the first non-whitespace line, look at
304 // the line we have. If it is not whitespace, and therefore
305 // part of the first paragraph, then ignore the indentation
306 // level of the first line
307 let ignore_previous_indents =
310 !line.is_whitespace();
312 let min_indent = if ignore_previous_indents {
319 saw_second_line = true;
322 if line.is_whitespace() {
325 saw_first_line = true;
327 line.chars().all(|char| {
328 // Only comparing against space because I wouldn't
329 // know what to do with mixed whitespace chars
337 cmp::min(min_indent, spaces)
341 if lines.len() >= 1 {
342 let mut unindented = vec![ lines.get(0).trim().to_string() ];
343 unindented.push_all(lines.tail().iter().map(|&line| {
344 if line.is_whitespace() {
347 assert!(line.len() >= min_indent);
348 line.slice_from(min_indent).to_string()
350 }).collect::<Vec<_>>().as_slice());
351 unindented.connect("\n").to_string()
362 fn should_unindent() {
363 let s = " line1\n line2".to_string();
364 let r = unindent(s.as_slice());
365 assert_eq!(r.as_slice(), "line1\nline2");
369 fn should_unindent_multiple_paragraphs() {
370 let s = " line1\n\n line2".to_string();
371 let r = unindent(s.as_slice());
372 assert_eq!(r.as_slice(), "line1\n\nline2");
376 fn should_leave_multiple_indent_levels() {
377 // Line 2 is indented another level beyond the
378 // base indentation and should be preserved
379 let s = " line1\n\n line2".to_string();
380 let r = unindent(s.as_slice());
381 assert_eq!(r.as_slice(), "line1\n\n line2");
385 fn should_ignore_first_line_indent() {
386 // Thi first line of the first paragraph may not be indented as
387 // far due to the way the doc string was written:
389 // #[doc = "Start way over here
390 // and continue here"]
391 let s = "line1\n line2".to_string();
392 let r = unindent(s.as_slice());
393 assert_eq!(r.as_slice(), "line1\nline2");
397 fn should_not_ignore_first_line_indent_in_a_single_line_para() {
398 let s = "line1\n\n line2".to_string();
399 let r = unindent(s.as_slice());
400 assert_eq!(r.as_slice(), "line1\n\n line2");