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.
12 use collections::HashSet;
16 use rustc::util::nodemap::NodeSet;
24 /// Strip items marked `#[doc(hidden)]`
25 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
26 let mut stripped = HashSet::new();
28 // strip all #[doc(hidden)] items
31 stripped: &'a mut HashSet<ast::NodeId>
33 impl<'a> fold::DocFolder for Stripper<'a> {
34 fn fold_item(&mut self, i: Item) -> Option<Item> {
35 for attr in i.attrs.iter() {
37 &clean::List(ref x, ref l) if "doc" == *x => {
38 for innerattr in l.iter() {
40 &clean::Word(ref s) if "hidden" == *s => {
41 debug!("found one in strip_hidden; removing");
42 self.stripped.insert(i.id);
52 self.fold_item_recur(i)
55 let mut stripper = Stripper{ stripped: &mut stripped };
56 stripper.fold_crate(krate)
59 // strip any traits implemented on stripped items
61 struct ImplStripper<'a> {
62 stripped: &'a mut HashSet<ast::NodeId>
64 impl<'a> fold::DocFolder for ImplStripper<'a> {
65 fn fold_item(&mut self, i: Item) -> Option<Item> {
67 clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: for_id, .. },
69 if self.stripped.contains(&for_id) {
75 self.fold_item_recur(i)
78 let mut stripper = ImplStripper{ stripped: &mut stripped };
79 stripper.fold_crate(krate)
85 /// Strip private items from the point of view of a crate or externally from a
86 /// crate, specified by the `xcrate` flag.
87 pub fn strip_private(krate: clean::Crate) -> plugins::PluginResult {
88 // This stripper collects all *retained* nodes.
89 let mut retained = HashSet::new();
90 let exported_items = local_data::get(super::analysiskey, |analysis| {
91 analysis.unwrap().exported_items.clone()
93 let mut krate = krate;
95 // strip all private items
97 let mut stripper = Stripper {
98 retained: &mut retained,
99 exported_items: &exported_items,
101 krate = stripper.fold_crate(krate);
104 // strip all private implementations of traits
106 let mut stripper = ImplStripper(&retained);
107 krate = stripper.fold_crate(krate);
112 struct Stripper<'a> {
113 retained: &'a mut HashSet<ast::NodeId>,
114 exported_items: &'a NodeSet,
117 impl<'a> fold::DocFolder for Stripper<'a> {
118 fn fold_item(&mut self, i: Item) -> Option<Item> {
120 // These items can all get re-exported
121 clean::TypedefItem(..) | clean::StaticItem(..) |
122 clean::StructItem(..) | clean::EnumItem(..) |
123 clean::TraitItem(..) | clean::FunctionItem(..) |
124 clean::VariantItem(..) | clean::MethodItem(..) |
125 clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
126 if !self.exported_items.contains(&i.id) {
131 clean::ViewItemItem(..) |
132 clean::ModuleItem(..) => {
133 if i.visibility != Some(ast::Public) {
138 clean::StructFieldItem(..) => {
139 if i.visibility == Some(ast::Private) {
144 // trait impls for private items should be stripped
145 clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: ref for_id, .. }, .. }) => {
146 if !self.exported_items.contains(for_id) {
150 clean::ImplItem(..) => {}
152 // tymethods/macros have no control over privacy
153 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
156 let fastreturn = match i.inner {
157 // nothing left to do for traits (don't want to filter their
158 // methods out, visibility controlled by the trait)
159 clean::TraitItem(..) => true,
161 // implementations of traits are always public.
162 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
167 let i = if fastreturn {
168 self.retained.insert(i.id);
171 self.fold_item_recur(i)
177 // emptied modules/impls have no need to exist
178 clean::ModuleItem(ref m)
179 if m.items.len() == 0 &&
180 i.doc_value().is_none() => None,
181 clean::ImplItem(ref i) if i.methods.len() == 0 => None,
183 self.retained.insert(i.id);
193 // This stripper discards all private impls of traits
194 struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
195 impl<'a> fold::DocFolder for ImplStripper<'a> {
196 fn fold_item(&mut self, i: Item) -> Option<Item> {
198 clean::ImplItem(ref imp) => {
200 Some(clean::ResolvedPath{ id, .. }) => {
201 let ImplStripper(s) = *self;
202 if !s.contains(&id) {
206 Some(..) | None => {}
211 self.fold_item_recur(i)
216 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
217 struct CommentCleaner;
218 impl fold::DocFolder for CommentCleaner {
219 fn fold_item(&mut self, i: Item) -> Option<Item> {
221 let mut avec: ~[clean::Attribute] = ~[];
222 for attr in i.attrs.iter() {
224 &clean::NameValue(ref x, ref s) if "doc" == *x => avec.push(
225 clean::NameValue(~"doc", unindent(*s))),
226 x => avec.push(x.clone())
230 self.fold_item_recur(i)
233 let mut cleaner = CommentCleaner;
234 let krate = cleaner.fold_crate(krate);
238 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
240 impl fold::DocFolder for Collapser {
241 fn fold_item(&mut self, i: Item) -> Option<Item> {
242 let mut docstr = ~"";
244 for attr in i.attrs.iter() {
246 clean::NameValue(ref x, ref s) if "doc" == *x => {
247 docstr.push_str(s.clone());
248 docstr.push_char('\n');
253 let mut a: ~[clean::Attribute] = i.attrs.iter().filter(|&a| match a {
254 &clean::NameValue(ref x, _) if "doc" == *x => false,
256 }).map(|x| x.clone()).collect();
258 a.push(clean::NameValue(~"doc", docstr));
261 self.fold_item_recur(i)
264 let mut collapser = Collapser;
265 let krate = collapser.fold_crate(krate);
269 pub fn unindent(s: &str) -> ~str {
270 let lines = s.lines_any().collect::<~[&str]>();
271 let mut saw_first_line = false;
272 let mut saw_second_line = false;
273 let min_indent = lines.iter().fold(uint::MAX, |min_indent, line| {
275 // After we see the first non-whitespace line, look at
276 // the line we have. If it is not whitespace, and therefore
277 // part of the first paragraph, then ignore the indentation
278 // level of the first line
279 let ignore_previous_indents =
282 !line.is_whitespace();
284 let min_indent = if ignore_previous_indents {
291 saw_second_line = true;
294 if line.is_whitespace() {
297 saw_first_line = true;
299 line.chars().all(|char| {
300 // Only comparing against space because I wouldn't
301 // know what to do with mixed whitespace chars
309 cmp::min(min_indent, spaces)
313 if lines.len() >= 1 {
314 let mut unindented = ~[ lines[0].trim() ];
315 unindented.push_all(lines.tail().map(|&line| {
316 if line.is_whitespace() {
319 assert!(line.len() >= min_indent);
320 line.slice_from(min_indent)
323 unindented.connect("\n")
334 fn should_unindent() {
335 let s = ~" line1\n line2";
337 assert_eq!(r, ~"line1\nline2");
341 fn should_unindent_multiple_paragraphs() {
342 let s = ~" line1\n\n line2";
344 assert_eq!(r, ~"line1\n\nline2");
348 fn should_leave_multiple_indent_levels() {
349 // Line 2 is indented another level beyond the
350 // base indentation and should be preserved
351 let s = ~" line1\n\n line2";
353 assert_eq!(r, ~"line1\n\n line2");
357 fn should_ignore_first_line_indent() {
358 // Thi first line of the first paragraph may not be indented as
359 // far due to the way the doc string was written:
361 // #[doc = "Start way over here
362 // and continue here"]
363 let s = ~"line1\n line2";
365 assert_eq!(r, ~"line1\nline2");
369 fn should_not_ignore_first_line_indent_in_a_single_line_para() {
370 let s = ~"line1\n\n line2";
372 assert_eq!(r, ~"line1\n\n line2");