rust/src/librustdoc/passes.rs

318 lines
10 KiB
Rust
Raw Normal View History

// Copyright 2012-2013 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 std::num;
use std::uint;
use std::hashmap::HashSet;
use syntax::ast;
2013-08-15 15:28:54 -05:00
use clean;
use clean::Item;
use plugins;
use fold;
use fold::DocFolder;
/// Strip items marked `#[doc(hidden)]`
pub fn strip_hidden(crate: clean::Crate) -> plugins::PluginResult {
struct Stripper;
impl fold::DocFolder for Stripper {
fn fold_item(&mut self, i: Item) -> Option<Item> {
for attr in i.attrs.iter() {
match attr {
&clean::List(~"doc", ref l) => {
for innerattr in l.iter() {
match innerattr {
&clean::Word(ref s) if "hidden" == *s => {
2013-09-28 01:22:34 -05:00
debug2!("found one in strip_hidden; removing");
2013-08-15 15:28:54 -05:00
return None;
},
_ => (),
}
}
},
_ => ()
}
}
self.fold_item_recur(i)
}
}
let mut stripper = Stripper;
let crate = stripper.fold_crate(crate);
(crate, 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(mut crate: clean::Crate) -> plugins::PluginResult {
// This stripper collects all *retained* nodes.
struct Stripper<'self>(&'self mut HashSet<ast::NodeId>);
impl<'self> fold::DocFolder for Stripper<'self> {
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::ViewItemItem(*) | clean::MethodItem(*) |
clean::ForeignFunctionItem(*) | clean::ForeignStaticItem(*) => {
// XXX: re-exported items should get surfaced in the docs as
// well (using the output of resolve analysis)
if i.visibility != Some(ast::public) {
return None;
}
}
// These are public-by-default (if the enum/struct was public)
clean::VariantItem(*) | clean::StructFieldItem(*) => {
if i.visibility == Some(ast::private) {
return None;
}
}
// handled below
clean::ModuleItem(*) => {}
// impls/tymethods have no control over privacy
clean::ImplItem(*) | 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.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 => None,
clean::ImplItem(ref i) if i.methods.len() == 0 => None,
_ => {
self.insert(i.id);
Some(i)
}
}
}
None => None,
}
}
}
// This stripper discards all private impls of traits
struct ImplStripper<'self>(&'self HashSet<ast::NodeId>);
impl<'self> fold::DocFolder for ImplStripper<'self> {
fn fold_item(&mut self, i: Item) -> Option<Item> {
match i.inner {
clean::ImplItem(ref imp) => {
match imp.trait_ {
rustdoc: Generate hyperlinks between crates The general idea of hyperlinking between crates is that it should require as little configuration as possible, if any at all. In this vein, there are two separate ways to generate hyperlinks between crates: 1. When you're generating documentation for a crate 'foo' into folder 'doc', then if foo's external crate dependencies already have documented in the folder 'doc', then hyperlinks will be generated. This will work because all documentation is in the same folder, allowing links to work seamlessly both on the web and on the local filesystem browser. The rationale for this use case is a package with multiple libraries/crates that all want to link to one another, and you don't want to have to deal with going to the web. In theory this could be extended to have a RUST_PATH-style searching situtation, but I'm not sure that it would work seamlessly on the web as it does on the local filesystem, so I'm not attempting to explore this case in this pull request. I believe to fully realize this potential rustdoc would have to be acting as a server instead of a static site generator. 2. One of foo's external dependencies has a #[doc(html_root_url = "...")] attribute. This means that all hyperlinks to the dependency will be rooted at this url. This use case encompasses all packages using libstd/libextra. These two crates now have this attribute encoded (currently at the /doc/master url) and will be read by anything which has a dependency on libstd/libextra. This should also work for arbitrary crates in the wild that have online documentation. I don't like how the version is hard-wired into the url, but I think that this may be a case-by-case thing which doesn't end up being too bad in the long run. Closes #9539
2013-10-02 17:39:32 -05:00
Some(clean::ResolvedPath{ id, _ }) => {
if !self.contains(&id) {
return None;
}
}
Some(*) | None => {}
}
}
_ => {}
}
self.fold_item_recur(i)
}
}
let mut retained = HashSet::new();
// First, strip all private items
{
let mut stripper = Stripper(&mut retained);
crate = stripper.fold_crate(crate);
}
// Next, strip all private implementations of traits
{
let mut stripper = ImplStripper(&retained);
crate = stripper.fold_crate(crate);
}
(crate, None)
}
pub fn unindent_comments(crate: clean::Crate) -> plugins::PluginResult {
2013-08-15 15:28:54 -05:00
struct CommentCleaner;
impl fold::DocFolder for CommentCleaner {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let mut i = i;
let mut avec: ~[clean::Attribute] = ~[];
for attr in i.attrs.iter() {
match attr {
&clean::NameValue(~"doc", ref s) => avec.push(
clean::NameValue(~"doc", unindent(*s))),
2013-08-15 15:28:54 -05:00
x => avec.push(x.clone())
}
}
i.attrs = avec;
self.fold_item_recur(i)
}
}
let mut cleaner = CommentCleaner;
let crate = cleaner.fold_crate(crate);
(crate, None)
}
pub fn collapse_docs(crate: clean::Crate) -> plugins::PluginResult {
struct Collapser;
impl fold::DocFolder for Collapser {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let mut docstr = ~"";
let mut i = i;
for attr in i.attrs.iter() {
match *attr {
clean::NameValue(~"doc", ref s) => {
docstr.push_str(s.clone());
docstr.push_char('\n');
},
_ => ()
}
}
let mut a: ~[clean::Attribute] = i.attrs.iter().filter(|&a| match a {
&clean::NameValue(~"doc", _) => false,
_ => true
}).map(|x| x.clone()).collect();
if "" != docstr {
a.push(clean::NameValue(~"doc", docstr));
2013-08-15 15:28:54 -05:00
}
i.attrs = a;
self.fold_item_recur(i)
}
}
let mut collapser = Collapser;
let crate = collapser.fold_crate(crate);
(crate, None)
}
pub fn unindent(s: &str) -> ~str {
let lines = s.any_line_iter().collect::<~[&str]>();
let mut saw_first_line = false;
let mut saw_second_line = false;
let min_indent = do lines.iter().fold(uint::max_value) |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_value
} else {
min_indent
};
2013-08-15 15:28:54 -05:00
if saw_first_line {
saw_second_line = true;
}
2013-08-15 15:28:54 -05:00
if line.is_whitespace() {
min_indent
} else {
saw_first_line = true;
let mut spaces = 0;
do line.iter().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
}
};
num::min(min_indent, spaces)
}
};
match lines {
[head, .. tail] => {
let mut unindented = ~[ head.trim() ];
unindented.push_all(do tail.map |&line| {
if line.is_whitespace() {
line
} else {
assert!(line.len() >= min_indent);
line.slice_from(min_indent)
}
});
unindented.connect("\n")
}
[] => s.to_owned()
2013-08-15 15:28:54 -05:00
}
}
2013-08-15 15:28:54 -05:00
#[cfg(test)]
mod unindent_tests {
use super::unindent;
2013-08-15 15:28:54 -05:00
#[test]
fn should_unindent() {
let s = ~" line1\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\nline2");
2013-08-15 15:28:54 -05:00
}
#[test]
fn should_unindent_multiple_paragraphs() {
let s = ~" line1\n\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\n\nline2");
}
2013-08-15 15:28:54 -05:00
#[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";
let r = unindent(s);
assert_eq!(r, ~"line1\n\n line2");
2013-08-15 15:28:54 -05:00
}
#[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";
let r = unindent(s);
assert_eq!(r, ~"line1\nline2");
2013-08-15 15:28:54 -05:00
}
#[test]
fn should_not_ignore_first_line_indent_in_a_single_line_para() {
let s = ~"line1\n\n line2";
let r = unindent(s);
assert_eq!(r, ~"line1\n\n line2");
}
2013-08-15 15:28:54 -05:00
}