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 rustc::middle::def_id::DefId;
12 use rustc::middle::privacy::AccessLevels;
13 use rustc::util::nodemap::DefIdSet;
15 use std::string::String;
19 use clean::{self, Attributes};
25 /// Strip items marked `#[doc(hidden)]`
26 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
27 let mut stripped = DefIdSet();
29 // strip all #[doc(hidden)] items
32 stripped: &'a mut DefIdSet
34 impl<'a> fold::DocFolder for Stripper<'a> {
35 fn fold_item(&mut self, i: Item) -> Option<Item> {
36 if i.attrs.list_def("doc").has_word("hidden") {
37 debug!("found one in strip_hidden; removing");
38 self.stripped.insert(i.def_id);
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 DefIdSet
66 impl<'a> fold::DocFolder for ImplStripper<'a> {
67 fn fold_item(&mut self, i: Item) -> Option<Item> {
68 if let clean::ImplItem(clean::Impl{
69 for_: clean::ResolvedPath{ did, .. },
72 // Impls for stripped types don't need to exist
73 if self.stripped.contains(&did) {
76 // Impls of stripped traits also don't need to exist
77 if let Some(clean::ResolvedPath { did, .. }) = *trait_ {
78 if self.stripped.contains(&did) {
83 self.fold_item_recur(i)
86 let mut stripper = ImplStripper{ stripped: &mut stripped };
87 stripper.fold_crate(krate)
93 /// Strip private items from the point of view of a crate or externally from a
94 /// crate, specified by the `xcrate` flag.
95 pub fn strip_private(mut krate: clean::Crate) -> plugins::PluginResult {
96 // This stripper collects all *retained* nodes.
97 let mut retained = DefIdSet();
98 let analysis = super::ANALYSISKEY.with(|a| a.clone());
99 let analysis = analysis.borrow();
100 let analysis = analysis.as_ref().unwrap();
101 let access_levels = analysis.access_levels.clone();
103 // strip all private items
105 let mut stripper = Stripper {
106 retained: &mut retained,
107 access_levels: &access_levels,
109 krate = stripper.fold_crate(krate);
112 // strip all private implementations of traits
114 let mut stripper = ImplStripper(&retained);
115 krate = stripper.fold_crate(krate);
120 struct Stripper<'a> {
121 retained: &'a mut DefIdSet,
122 access_levels: &'a AccessLevels<DefId>,
125 impl<'a> fold::DocFolder for Stripper<'a> {
126 fn fold_item(&mut self, i: Item) -> Option<Item> {
128 // These items can all get re-exported
129 clean::TypedefItem(..) | clean::StaticItem(..) |
130 clean::StructItem(..) | clean::EnumItem(..) |
131 clean::TraitItem(..) | clean::FunctionItem(..) |
132 clean::VariantItem(..) | clean::MethodItem(..) |
133 clean::ForeignFunctionItem(..) | clean::ForeignStaticItem(..) => {
134 if i.def_id.is_local() {
135 if !self.access_levels.is_exported(i.def_id) {
141 clean::ConstantItem(..) => {
142 if i.def_id.is_local() && !self.access_levels.is_exported(i.def_id) {
147 clean::ExternCrateItem(..) | clean::ImportItem(_) => {
148 if i.visibility != Some(hir::Public) {
153 clean::StructFieldItem(..) => {
154 if i.visibility != Some(hir::Public) {
155 return Some(clean::Item {
156 inner: clean::StructFieldItem(clean::HiddenStructField),
163 clean::ModuleItem(..) => {}
165 // trait impls for private items should be stripped
166 clean::ImplItem(clean::Impl{
167 for_: clean::ResolvedPath{ did, .. }, ..
169 if did.is_local() && !self.access_levels.is_exported(did) {
173 clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
175 // tymethods/macros have no control over privacy
176 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
178 // Primitives are never stripped
179 clean::PrimitiveItem(..) => {}
181 // Associated consts and types are never stripped
182 clean::AssociatedConstItem(..) |
183 clean::AssociatedTypeItem(..) => {}
186 let fastreturn = match i.inner {
187 // nothing left to do for traits (don't want to filter their
188 // methods out, visibility controlled by the trait)
189 clean::TraitItem(..) => true,
191 // implementations of traits are always public.
192 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
194 // Struct variant fields have inherited visibility
195 clean::VariantItem(clean::Variant {
196 kind: clean::StructVariant(..)
201 let i = if fastreturn {
202 self.retained.insert(i.def_id);
205 self.fold_item_recur(i)
210 // emptied modules/impls have no need to exist
211 clean::ModuleItem(ref m)
212 if m.items.is_empty() &&
213 i.doc_value().is_none() => None,
214 clean::ImplItem(ref i) if i.items.is_empty() => None,
216 self.retained.insert(i.def_id);
224 // This stripper discards all private impls of traits
225 struct ImplStripper<'a>(&'a DefIdSet);
226 impl<'a> fold::DocFolder for ImplStripper<'a> {
227 fn fold_item(&mut self, i: Item) -> Option<Item> {
228 if let clean::ImplItem(ref imp) = i.inner {
230 Some(clean::ResolvedPath{ did, .. }) => {
231 if did.is_local() && !self.0.contains(&did) {
235 Some(..) | None => {}
238 self.fold_item_recur(i)
243 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
244 struct CommentCleaner;
245 impl fold::DocFolder for CommentCleaner {
246 fn fold_item(&mut self, mut i: Item) -> Option<Item> {
247 let mut avec: Vec<clean::Attribute> = Vec::new();
248 for attr in &i.attrs {
250 &clean::NameValue(ref x, ref s)
252 avec.push(clean::NameValue("doc".to_string(),
255 x => avec.push(x.clone())
259 self.fold_item_recur(i)
262 let mut cleaner = CommentCleaner;
263 let krate = cleaner.fold_crate(krate);
267 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
269 impl fold::DocFolder for Collapser {
270 fn fold_item(&mut self, mut i: Item) -> Option<Item> {
271 let mut docstr = String::new();
272 for attr in &i.attrs {
273 if let clean::NameValue(ref x, ref s) = *attr {
280 let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
281 &clean::NameValue(ref x, _) if "doc" == *x => false,
283 }).cloned().collect();
284 if !docstr.is_empty() {
285 a.push(clean::NameValue("doc".to_string(), docstr));
288 self.fold_item_recur(i)
291 let mut collapser = Collapser;
292 let krate = collapser.fold_crate(krate);
296 pub fn unindent(s: &str) -> String {
297 let lines = s.lines().collect::<Vec<&str> >();
298 let mut saw_first_line = false;
299 let mut saw_second_line = false;
300 let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
302 // After we see the first non-whitespace line, look at
303 // the line we have. If it is not whitespace, and therefore
304 // part of the first paragraph, then ignore the indentation
305 // level of the first line
306 let ignore_previous_indents =
309 !line.chars().all(|c| c.is_whitespace());
311 let min_indent = if ignore_previous_indents {
318 saw_second_line = true;
321 if line.chars().all(|c| c.is_whitespace()) {
324 saw_first_line = true;
325 let mut whitespace = 0;
326 line.chars().all(|char| {
327 // Compare against either space or tab, ignoring whether they
329 if char == ' ' || char == '\t' {
336 cmp::min(min_indent, whitespace)
340 if !lines.is_empty() {
341 let mut unindented = vec![ lines[0].trim().to_string() ];
342 unindented.extend_from_slice(&lines[1..].iter().map(|&line| {
343 if line.chars().all(|c| c.is_whitespace()) {
346 assert!(line.len() >= min_indent);
347 line[min_indent..].to_string()
349 }).collect::<Vec<_>>());
350 unindented.join("\n")
361 fn should_unindent() {
362 let s = " line1\n line2".to_string();
363 let r = unindent(&s);
364 assert_eq!(r, "line1\nline2");
368 fn should_unindent_multiple_paragraphs() {
369 let s = " line1\n\n line2".to_string();
370 let r = unindent(&s);
371 assert_eq!(r, "line1\n\nline2");
375 fn should_leave_multiple_indent_levels() {
376 // Line 2 is indented another level beyond the
377 // base indentation and should be preserved
378 let s = " line1\n\n line2".to_string();
379 let r = unindent(&s);
380 assert_eq!(r, "line1\n\n line2");
384 fn should_ignore_first_line_indent() {
385 // The first line of the first paragraph may not be indented as
386 // far due to the way the doc string was written:
388 // #[doc = "Start way over here
389 // and continue here"]
390 let s = "line1\n line2".to_string();
391 let r = unindent(&s);
392 assert_eq!(r, "line1\nline2");
396 fn should_not_ignore_first_line_indent_in_a_single_line_para() {
397 let s = "line1\n\n line2".to_string();
398 let r = unindent(&s);
399 assert_eq!(r, "line1\n\n line2");
403 fn should_unindent_tabs() {
404 let s = "\tline1\n\tline2".to_string();
405 let r = unindent(&s);
406 assert_eq!(r, "line1\nline2");
410 fn should_trim_mixed_indentation() {
411 let s = "\t line1\n\t line2".to_string();
412 let r = unindent(&s);
413 assert_eq!(r, "line1\nline2");
415 let s = " \tline1\n \tline2".to_string();
416 let r = unindent(&s);
417 assert_eq!(r, "line1\nline2");