rust/src/librustdoc/passes.rs

415 lines
14 KiB
Rust
Raw Normal View History

// 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 std::collections::HashSet;
use rustc::util::nodemap::NodeSet;
use std::cmp;
use std::string::String;
use std::usize;
use syntax::ast;
use syntax::ast_util;
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(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> {
if i.is_hidden_from_doc() {
debug!("found one in strip_hidden; removing");
self.stripped.insert(i.def_id.node);
// use a dedicated hidden item for given item type if any
match i.inner {
clean::StructFieldItem(..) => {
return Some(clean::Item {
inner: clean::StructFieldItem(clean::HiddenStructField),
..i
});
}
_ => {
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> {
if let clean::ImplItem(clean::Impl{
for_: clean::ResolvedPath{ did, .. },
ref trait_, ..
}) = i.inner {
// Impls for stripped types don't need to exist
if self.stripped.contains(&did.node) {
return None;
}
// Impls of stripped traits also don't need to exist
if let Some(clean::ResolvedPath { did, .. }) = *trait_ {
if self.stripped.contains(&did.node) {
return None;
2013-08-15 15:28:54 -05:00
}
}
2013-08-15 15:28:54 -05:00
}
self.fold_item_recur(i)
2013-08-15 15:28:54 -05:00
}
}
let mut stripper = ImplStripper{ stripped: &mut stripped };
stripper.fold_crate(krate)
};
(krate, None)
2013-08-15 15:28:54 -05:00
}
/// 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 krate: clean::Crate) -> plugins::PluginResult {
// This stripper collects all *retained* nodes.
let mut retained = HashSet::new();
let analysis = super::ANALYSISKEY.with(|a| a.clone());
let analysis = analysis.borrow();
let analysis = analysis.as_ref().unwrap();
let exported_items = analysis.exported_items.clone();
// 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
2013-11-28 14:22:53 -06:00
clean::TypedefItem(..) | clean::StaticItem(..) |
clean::StructItem(..) | clean::EnumItem(..) |
clean::TraitItem(..) | clean::FunctionItem(..) |
clean::VariantItem(..) | clean::MethodItem(..) |
clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
if ast_util::is_local(i.def_id) {
if !self.exported_items.contains(&i.def_id.node) {
return None;
}
// Traits are in exported_items even when they're totally private.
if i.is_trait() && i.visibility != Some(ast::Public) {
return None;
}
}
}
clean::ConstantItem(..) => {
if ast_util::is_local(i.def_id) &&
!self.exported_items.contains(&i.def_id.node) {
return None;
}
}
clean::ExternCrateItem(..) | clean::ImportItem(_) => {
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{ did, .. }, ..
}) => {
if ast_util::is_local(did) &&
!self.exported_items.contains(&did.node) {
return None;
}
}
clean::ImplItem(..) => {}
// tymethods/macros have no control over privacy
clean::MacroItem(..) | clean::TyMethodItem(..) => {}
// Primitives are never stripped
clean::PrimitiveItem(..) => {}
// Associated types are never stripped
clean::AssociatedTypeItem(..) => {}
}
let fastreturn = match i.inner {
// nothing left to do for traits (don't want to filter their
// methods out, visibility controlled by the trait)
2013-11-28 14:22:53 -06:00
clean::TraitItem(..) => true,
// implementations of traits are always public.
clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
// Struct variant fields have inherited visibility
clean::VariantItem(clean::Variant {
kind: clean::StructVariant(..)
}) => true,
_ => false,
};
let i = if fastreturn {
self.retained.insert(i.def_id.node);
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.items.len() == 0 => None,
_ => {
self.retained.insert(i.def_id.node);
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> {
if let clean::ImplItem(ref imp) = i.inner {
match imp.trait_ {
Some(clean::ResolvedPath{ did, .. }) => {
let ImplStripper(s) = *self;
if ast_util::is_local(did) && !s.contains(&did.node) {
return None;
}
}
Some(..) | None => {}
}
}
self.fold_item_recur(i)
}
}
pub fn unindent_comments(krate: 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: Vec<clean::Attribute> = Vec::new();
2015-01-31 11:20:46 -06:00
for attr in &i.attrs {
2013-08-15 15:28:54 -05:00
match attr {
&clean::NameValue(ref x, ref s)
if "doc" == *x => {
avec.push(clean::NameValue("doc".to_string(),
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 krate = cleaner.fold_crate(krate);
(krate, None)
2013-08-15 15:28:54 -05:00
}
pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
2013-08-15 15:28:54 -05:00
struct Collapser;
impl fold::DocFolder for Collapser {
fn fold_item(&mut self, i: Item) -> Option<Item> {
let mut docstr = String::new();
2013-08-15 15:28:54 -05:00
let mut i = i;
2015-01-31 11:20:46 -06:00
for attr in &i.attrs {
2013-08-15 15:28:54 -05:00
match *attr {
clean::NameValue(ref x, ref s)
if "doc" == *x => {
docstr.push_str(s);
docstr.push('\n');
2013-08-15 15:28:54 -05:00
},
_ => ()
}
}
let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
&clean::NameValue(ref x, _) if "doc" == *x => false,
2013-08-15 15:28:54 -05:00
_ => true
}).map(|x| x.clone()).collect();
if docstr.len() > 0 {
a.push(clean::NameValue("doc".to_string(), docstr));
2013-08-15 15:28:54 -05:00
}
i.attrs = a;
self.fold_item_recur(i)
}
}
let mut collapser = Collapser;
let krate = collapser.fold_crate(krate);
(krate, None)
2013-08-15 15:28:54 -05:00
}
pub fn unindent(s: &str) -> String {
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(usize::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 &&
2014-12-10 21:46:38 -06:00
!line.chars().all(|c| c.is_whitespace());
let min_indent = if ignore_previous_indents {
usize::MAX
} 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
2014-12-10 21:46:38 -06:00
if line.chars().all(|c| c.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
}
});
2014-02-06 01:34:33 -06:00
cmp::min(min_indent, spaces)
}
});
if lines.len() >= 1 {
2014-07-14 18:37:25 -05:00
let mut unindented = vec![ lines[0].trim().to_string() ];
unindented.push_all(&lines.tail().iter().map(|&line| {
2014-12-10 21:46:38 -06:00
if line.chars().all(|c| c.is_whitespace()) {
line.to_string()
} else {
assert!(line.len() >= min_indent);
2015-01-17 18:15:52 -06:00
line[min_indent..].to_string()
}
}).collect::<Vec<_>>());
unindented.connect("\n")
} else {
s.to_string()
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() {
2014-05-25 05:10:11 -05:00
let s = " line1\n line2".to_string();
let r = unindent(&s);
assert_eq!(r, "line1\nline2");
2013-08-15 15:28:54 -05:00
}
#[test]
fn should_unindent_multiple_paragraphs() {
2014-05-25 05:10:11 -05:00
let s = " line1\n\n line2".to_string();
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
2014-05-25 05:10:11 -05:00
let s = " line1\n\n line2".to_string();
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() {
// The 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"]
2014-05-25 05:10:11 -05:00
let s = "line1\n line2".to_string();
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() {
2014-05-25 05:10:11 -05:00
let s = "line1\n\n line2".to_string();
let r = unindent(&s);
assert_eq!(r, "line1\n\n line2");
}
2013-08-15 15:28:54 -05:00
}