rust/src/librustdoc/passes.rs
Alex Crichton 9d546d60c1 rustdoc: Display tuple structs correctly
The fields of tuple structs recently gained the ability to have privacy
associated with them, but rustdoc was not updated accodingly. This moves the
struct field filtering to the rendering phase in order to preserve the ordering
of struct fields to allow tuple structs to have their private fields printed as
underscores.

Closes #13594
2014-04-20 12:38:44 -07:00

381 lines
12 KiB
Rust

// Copyright 2012-2014 The Rust Project Developers. See the COPYRIGHT
// file at the top-level directory of this distribution and at
// http://rust-lang.org/COPYRIGHT.
//
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
// option. This file may not be copied, modified, or distributed
// except according to those terms.
use collections::HashSet;
use rustc::util::nodemap::NodeSet;
use std::cmp;
use std::local_data;
use std::strbuf::StrBuf;
use std::uint;
use syntax::ast;
use clean;
use clean::Item;
use plugins;
use fold;
use fold::DocFolder;
/// Strip items marked `#[doc(hidden)]`
pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
let mut stripped = HashSet::new();
// strip all #[doc(hidden)] items
let krate = {
struct Stripper<'a> {
stripped: &'a mut HashSet<ast::NodeId>
};
impl<'a> fold::DocFolder for Stripper<'a> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
for attr in i.attrs.iter() {
match attr {
&clean::List(ref x, ref l) if "doc" == *x => {
for innerattr in l.iter() {
match innerattr {
&clean::Word(ref s) if "hidden" == *s => {
debug!("found one in strip_hidden; removing");
self.stripped.insert(i.id);
return None;
},
_ => (),
}
}
},
_ => ()
}
}
self.fold_item_recur(i)
}
}
let mut stripper = Stripper{ stripped: &mut stripped };
stripper.fold_crate(krate)
};
// strip any traits implemented on stripped items
let krate = {
struct ImplStripper<'a> {
stripped: &'a mut HashSet<ast::NodeId>
};
impl<'a> fold::DocFolder for ImplStripper<'a> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match i.inner {
clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: for_id, .. },
.. }) => {
if self.stripped.contains(&for_id) {
return None;
}
}
_ => {}
}
self.fold_item_recur(i)
}
}
let mut stripper = ImplStripper{ stripped: &mut stripped };
stripper.fold_crate(krate)
};
(krate, None)
}
/// Strip private items from the point of view of a crate or externally from a
/// crate, specified by the `xcrate` flag.
pub fn strip_private(krate: clean::Crate) -> plugins::PluginResult {
// This stripper collects all *retained* nodes.
let mut retained = HashSet::new();
let exported_items = local_data::get(super::analysiskey, |analysis| {
analysis.unwrap().exported_items.clone()
});
let mut krate = krate;
// strip all private items
{
let mut stripper = Stripper {
retained: &mut retained,
exported_items: &exported_items,
};
krate = stripper.fold_crate(krate);
}
// strip all private implementations of traits
{
let mut stripper = ImplStripper(&retained);
krate = stripper.fold_crate(krate);
}
(krate, None)
}
struct Stripper<'a> {
retained: &'a mut HashSet<ast::NodeId>,
exported_items: &'a NodeSet,
}
impl<'a> fold::DocFolder for Stripper<'a> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match i.inner {
// These items can all get re-exported
clean::TypedefItem(..) | clean::StaticItem(..) |
clean::StructItem(..) | clean::EnumItem(..) |
clean::TraitItem(..) | clean::FunctionItem(..) |
clean::VariantItem(..) | clean::MethodItem(..) |
clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
if !self.exported_items.contains(&i.id) {
return None;
}
}
clean::ViewItemItem(..) => {
if i.visibility != Some(ast::Public) {
return None
}
}
clean::StructFieldItem(..) => {
if i.visibility != Some(ast::Public) {
return Some(clean::Item {
inner: clean::StructFieldItem(clean::HiddenStructField),
..i
})
}
}
// handled below
clean::ModuleItem(..) => {}
// trait impls for private items should be stripped
clean::ImplItem(clean::Impl{ for_: clean::ResolvedPath{ id: ref for_id, .. }, .. }) => {
if !self.exported_items.contains(for_id) {
return None;
}
}
clean::ImplItem(..) => {}
// tymethods/macros have no control over privacy
clean::MacroItem(..) | clean::TyMethodItem(..) => {}
}
let fastreturn = match i.inner {
// nothing left to do for traits (don't want to filter their
// methods out, visibility controlled by the trait)
clean::TraitItem(..) => true,
// implementations of traits are always public.
clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
_ => false,
};
let i = if fastreturn {
self.retained.insert(i.id);
return Some(i);
} else {
self.fold_item_recur(i)
};
match i {
Some(i) => {
match i.inner {
// emptied modules/impls have no need to exist
clean::ModuleItem(ref m)
if m.items.len() == 0 &&
i.doc_value().is_none() => None,
clean::ImplItem(ref i) if i.methods.len() == 0 => None,
_ => {
self.retained.insert(i.id);
Some(i)
}
}
}
None => None,
}
}
}
// This stripper discards all private impls of traits
struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
impl<'a> fold::DocFolder for ImplStripper<'a> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match i.inner {
clean::ImplItem(ref imp) => {
match imp.trait_ {
Some(clean::ResolvedPath{ id, .. }) => {
let ImplStripper(s) = *self;
if !s.contains(&id) {
return None;
}
}
Some(..) | None => {}
}
}
_ => {}
}
self.fold_item_recur(i)
}
}
pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
struct CommentCleaner;
impl fold::DocFolder for CommentCleaner {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let mut i = i;
let mut avec: Vec<clean::Attribute> = Vec::new();
for attr in i.attrs.iter() {
match attr {
&clean::NameValue(ref x, ref s) if "doc" == *x => avec.push(
clean::NameValue("doc".to_owned(), unindent(*s))),
x => avec.push(x.clone())
}
}
i.attrs = avec;
self.fold_item_recur(i)
}
}
let mut cleaner = CommentCleaner;
let krate = cleaner.fold_crate(krate);
(krate, None)
}
pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
struct Collapser;
impl fold::DocFolder for Collapser {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let mut docstr = StrBuf::new();
let mut i = i;
for attr in i.attrs.iter() {
match *attr {
clean::NameValue(ref x, ref s) if "doc" == *x => {
docstr.push_str(s.clone());
docstr.push_char('\n');
},
_ => ()
}
}
let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
&clean::NameValue(ref x, _) if "doc" == *x => false,
_ => true
}).map(|x| x.clone()).collect();
if docstr.len() > 0 {
a.push(clean::NameValue("doc".to_owned(), docstr.into_owned()));
}
i.attrs = a;
self.fold_item_recur(i)
}
}
let mut collapser = Collapser;
let krate = collapser.fold_crate(krate);
(krate, None)
}
pub fn unindent(s: &str) -> ~str {
let lines = s.lines_any().collect::<Vec<&str> >();
let mut saw_first_line = false;
let mut saw_second_line = false;
let min_indent = lines.iter().fold(uint::MAX, |min_indent, line| {
// After we see the first non-whitespace line, look at
// the line we have. If it is not whitespace, and therefore
// part of the first paragraph, then ignore the indentation
// level of the first line
let ignore_previous_indents =
saw_first_line &&
!saw_second_line &&
!line.is_whitespace();
let min_indent = if ignore_previous_indents {
uint::MAX
} else {
min_indent
};
if saw_first_line {
saw_second_line = true;
}
if line.is_whitespace() {
min_indent
} else {
saw_first_line = true;
let mut spaces = 0;
line.chars().all(|char| {
// Only comparing against space because I wouldn't
// know what to do with mixed whitespace chars
if char == ' ' {
spaces += 1;
true
} else {
false
}
});
cmp::min(min_indent, spaces)
}
});
if lines.len() >= 1 {
let mut unindented = vec!( lines.get(0).trim() );
unindented.push_all(lines.tail().iter().map(|&line| {
if line.is_whitespace() {
line
} else {
assert!(line.len() >= min_indent);
line.slice_from(min_indent)
}
}).collect::<Vec<_>>().as_slice());
unindented.connect("\n")
} else {
s.to_owned()
}
}
#[cfg(test)]
mod unindent_tests {
use super::unindent;
#[test]
fn should_unindent() {
let s = " line1\n line2".to_owned();
let r = unindent(s);
assert_eq!(r, "line1\nline2".to_owned());
}
#[test]
fn should_unindent_multiple_paragraphs() {
let s = " line1\n\n line2".to_owned();
let r = unindent(s);
assert_eq!(r, "line1\n\nline2".to_owned());
}
#[test]
fn should_leave_multiple_indent_levels() {
// Line 2 is indented another level beyond the
// base indentation and should be preserved
let s = " line1\n\n line2".to_owned();
let r = unindent(s);
assert_eq!(r, "line1\n\n line2".to_owned());
}
#[test]
fn should_ignore_first_line_indent() {
// Thi first line of the first paragraph may not be indented as
// far due to the way the doc string was written:
//
// #[doc = "Start way over here
// and continue here"]
let s = "line1\n line2".to_owned();
let r = unindent(s);
assert_eq!(r, "line1\nline2".to_owned());
}
#[test]
fn should_not_ignore_first_line_indent_in_a_single_line_para() {
let s = "line1\n\n line2".to_owned();
let r = unindent(s);
assert_eq!(r, "line1\n\n line2".to_owned());
}
}