]> git.proxmox.com Git - rustc.git/blob - src/librustdoc/passes.rs
74c16127f41ccac87c3bf3aba6d2acb37b644c7e
[rustc.git] / src / librustdoc / passes.rs
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.
4 //
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.
10
11 use std::collections::HashSet;
12 use rustc::util::nodemap::NodeSet;
13 use std::cmp;
14 use std::string::String;
15 use std::usize;
16 use syntax::ast;
17 use syntax::ast_util;
18
19 use clean;
20 use clean::Item;
21 use plugins;
22 use fold;
23 use fold::DocFolder;
24
25 /// Strip items marked `#[doc(hidden)]`
26 pub fn strip_hidden(krate: clean::Crate) -> plugins::PluginResult {
27 let mut stripped = HashSet::new();
28
29 // strip all #[doc(hidden)] items
30 let krate = {
31 struct Stripper<'a> {
32 stripped: &'a mut HashSet<ast::NodeId>
33 };
34 impl<'a> fold::DocFolder for Stripper<'a> {
35 fn fold_item(&mut self, i: Item) -> Option<Item> {
36 if i.is_hidden_from_doc() {
37 debug!("found one in strip_hidden; removing");
38 self.stripped.insert(i.def_id.node);
39
40 // use a dedicated hidden item for given item type if any
41 match i.inner {
42 clean::StructFieldItem(..) => {
43 return Some(clean::Item {
44 inner: clean::StructFieldItem(clean::HiddenStructField),
45 ..i
46 });
47 }
48 _ => {
49 return None;
50 }
51 }
52 }
53
54 self.fold_item_recur(i)
55 }
56 }
57 let mut stripper = Stripper{ stripped: &mut stripped };
58 stripper.fold_crate(krate)
59 };
60
61 // strip any traits implemented on stripped items
62 let krate = {
63 struct ImplStripper<'a> {
64 stripped: &'a mut HashSet<ast::NodeId>
65 };
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, .. },
70 ref trait_, ..
71 }) = i.inner {
72 // Impls for stripped types don't need to exist
73 if self.stripped.contains(&did.node) {
74 return None;
75 }
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.node) {
79 return None;
80 }
81 }
82 }
83 self.fold_item_recur(i)
84 }
85 }
86 let mut stripper = ImplStripper{ stripped: &mut stripped };
87 stripper.fold_crate(krate)
88 };
89
90 (krate, None)
91 }
92
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 = HashSet::new();
98 let analysis = super::ANALYSISKEY.with(|a| a.clone());
99 let analysis = analysis.borrow();
100 let analysis = analysis.as_ref().unwrap();
101 let exported_items = analysis.exported_items.clone();
102
103 // strip all private items
104 {
105 let mut stripper = Stripper {
106 retained: &mut retained,
107 exported_items: &exported_items,
108 };
109 krate = stripper.fold_crate(krate);
110 }
111
112 // strip all private implementations of traits
113 {
114 let mut stripper = ImplStripper(&retained);
115 krate = stripper.fold_crate(krate);
116 }
117 (krate, None)
118 }
119
120 struct Stripper<'a> {
121 retained: &'a mut HashSet<ast::NodeId>,
122 exported_items: &'a NodeSet,
123 }
124
125 impl<'a> fold::DocFolder for Stripper<'a> {
126 fn fold_item(&mut self, i: Item) -> Option<Item> {
127 match i.inner {
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 ast_util::is_local(i.def_id) {
135 if !self.exported_items.contains(&i.def_id.node) {
136 return None;
137 }
138 // Traits are in exported_items even when they're totally private.
139 if i.is_trait() && i.visibility != Some(ast::Public) {
140 return None;
141 }
142 }
143 }
144
145 clean::ConstantItem(..) => {
146 if ast_util::is_local(i.def_id) &&
147 !self.exported_items.contains(&i.def_id.node) {
148 return None;
149 }
150 }
151
152 clean::ExternCrateItem(..) | clean::ImportItem(_) => {
153 if i.visibility != Some(ast::Public) {
154 return None
155 }
156 }
157
158 clean::StructFieldItem(..) => {
159 if i.visibility != Some(ast::Public) {
160 return Some(clean::Item {
161 inner: clean::StructFieldItem(clean::HiddenStructField),
162 ..i
163 })
164 }
165 }
166
167 // handled below
168 clean::ModuleItem(..) => {}
169
170 // trait impls for private items should be stripped
171 clean::ImplItem(clean::Impl{
172 for_: clean::ResolvedPath{ did, .. }, ..
173 }) => {
174 if ast_util::is_local(did) &&
175 !self.exported_items.contains(&did.node) {
176 return None;
177 }
178 }
179 clean::DefaultImplItem(..) | clean::ImplItem(..) => {}
180
181 // tymethods/macros have no control over privacy
182 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
183
184 // Primitives are never stripped
185 clean::PrimitiveItem(..) => {}
186
187 // Associated consts and types are never stripped
188 clean::AssociatedConstItem(..) |
189 clean::AssociatedTypeItem(..) => {}
190 }
191
192 let fastreturn = match i.inner {
193 // nothing left to do for traits (don't want to filter their
194 // methods out, visibility controlled by the trait)
195 clean::TraitItem(..) => true,
196
197 // implementations of traits are always public.
198 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
199
200 // Struct variant fields have inherited visibility
201 clean::VariantItem(clean::Variant {
202 kind: clean::StructVariant(..)
203 }) => true,
204 _ => false,
205 };
206
207 let i = if fastreturn {
208 self.retained.insert(i.def_id.node);
209 return Some(i);
210 } else {
211 self.fold_item_recur(i)
212 };
213
214 match i {
215 Some(i) => {
216 match i.inner {
217 // emptied modules/impls have no need to exist
218 clean::ModuleItem(ref m)
219 if m.items.is_empty() &&
220 i.doc_value().is_none() => None,
221 clean::ImplItem(ref i) if i.items.is_empty() => None,
222 _ => {
223 self.retained.insert(i.def_id.node);
224 Some(i)
225 }
226 }
227 }
228 None => None,
229 }
230 }
231 }
232
233 // This stripper discards all private impls of traits
234 struct ImplStripper<'a>(&'a HashSet<ast::NodeId>);
235 impl<'a> fold::DocFolder for ImplStripper<'a> {
236 fn fold_item(&mut self, i: Item) -> Option<Item> {
237 if let clean::ImplItem(ref imp) = i.inner {
238 match imp.trait_ {
239 Some(clean::ResolvedPath{ did, .. }) => {
240 let ImplStripper(s) = *self;
241 if ast_util::is_local(did) && !s.contains(&did.node) {
242 return None;
243 }
244 }
245 Some(..) | None => {}
246 }
247 }
248 self.fold_item_recur(i)
249 }
250 }
251
252
253 pub fn unindent_comments(krate: clean::Crate) -> plugins::PluginResult {
254 struct CommentCleaner;
255 impl fold::DocFolder for CommentCleaner {
256 fn fold_item(&mut self, i: Item) -> Option<Item> {
257 let mut i = i;
258 let mut avec: Vec<clean::Attribute> = Vec::new();
259 for attr in &i.attrs {
260 match attr {
261 &clean::NameValue(ref x, ref s)
262 if "doc" == *x => {
263 avec.push(clean::NameValue("doc".to_string(),
264 unindent(s)))
265 }
266 x => avec.push(x.clone())
267 }
268 }
269 i.attrs = avec;
270 self.fold_item_recur(i)
271 }
272 }
273 let mut cleaner = CommentCleaner;
274 let krate = cleaner.fold_crate(krate);
275 (krate, None)
276 }
277
278 pub fn collapse_docs(krate: clean::Crate) -> plugins::PluginResult {
279 struct Collapser;
280 impl fold::DocFolder for Collapser {
281 fn fold_item(&mut self, i: Item) -> Option<Item> {
282 let mut docstr = String::new();
283 let mut i = i;
284 for attr in &i.attrs {
285 match *attr {
286 clean::NameValue(ref x, ref s)
287 if "doc" == *x => {
288 docstr.push_str(s);
289 docstr.push('\n');
290 },
291 _ => ()
292 }
293 }
294 let mut a: Vec<clean::Attribute> = i.attrs.iter().filter(|&a| match a {
295 &clean::NameValue(ref x, _) if "doc" == *x => false,
296 _ => true
297 }).cloned().collect();
298 if !docstr.is_empty() {
299 a.push(clean::NameValue("doc".to_string(), docstr));
300 }
301 i.attrs = a;
302 self.fold_item_recur(i)
303 }
304 }
305 let mut collapser = Collapser;
306 let krate = collapser.fold_crate(krate);
307 (krate, None)
308 }
309
310 pub fn unindent(s: &str) -> String {
311 let lines = s.lines_any().collect::<Vec<&str> >();
312 let mut saw_first_line = false;
313 let mut saw_second_line = false;
314 let min_indent = lines.iter().fold(usize::MAX, |min_indent, line| {
315
316 // After we see the first non-whitespace line, look at
317 // the line we have. If it is not whitespace, and therefore
318 // part of the first paragraph, then ignore the indentation
319 // level of the first line
320 let ignore_previous_indents =
321 saw_first_line &&
322 !saw_second_line &&
323 !line.chars().all(|c| c.is_whitespace());
324
325 let min_indent = if ignore_previous_indents {
326 usize::MAX
327 } else {
328 min_indent
329 };
330
331 if saw_first_line {
332 saw_second_line = true;
333 }
334
335 if line.chars().all(|c| c.is_whitespace()) {
336 min_indent
337 } else {
338 saw_first_line = true;
339 let mut spaces = 0;
340 line.chars().all(|char| {
341 // Only comparing against space because I wouldn't
342 // know what to do with mixed whitespace chars
343 if char == ' ' {
344 spaces += 1;
345 true
346 } else {
347 false
348 }
349 });
350 cmp::min(min_indent, spaces)
351 }
352 });
353
354 if !lines.is_empty() {
355 let mut unindented = vec![ lines[0].trim().to_string() ];
356 unindented.push_all(&lines.tail().iter().map(|&line| {
357 if line.chars().all(|c| c.is_whitespace()) {
358 line.to_string()
359 } else {
360 assert!(line.len() >= min_indent);
361 line[min_indent..].to_string()
362 }
363 }).collect::<Vec<_>>());
364 unindented.connect("\n")
365 } else {
366 s.to_string()
367 }
368 }
369
370 #[cfg(test)]
371 mod unindent_tests {
372 use super::unindent;
373
374 #[test]
375 fn should_unindent() {
376 let s = " line1\n line2".to_string();
377 let r = unindent(&s);
378 assert_eq!(r, "line1\nline2");
379 }
380
381 #[test]
382 fn should_unindent_multiple_paragraphs() {
383 let s = " line1\n\n line2".to_string();
384 let r = unindent(&s);
385 assert_eq!(r, "line1\n\nline2");
386 }
387
388 #[test]
389 fn should_leave_multiple_indent_levels() {
390 // Line 2 is indented another level beyond the
391 // base indentation and should be preserved
392 let s = " line1\n\n line2".to_string();
393 let r = unindent(&s);
394 assert_eq!(r, "line1\n\n line2");
395 }
396
397 #[test]
398 fn should_ignore_first_line_indent() {
399 // The first line of the first paragraph may not be indented as
400 // far due to the way the doc string was written:
401 //
402 // #[doc = "Start way over here
403 // and continue here"]
404 let s = "line1\n line2".to_string();
405 let r = unindent(&s);
406 assert_eq!(r, "line1\nline2");
407 }
408
409 #[test]
410 fn should_not_ignore_first_line_indent_in_a_single_line_para() {
411 let s = "line1\n\n line2".to_string();
412 let r = unindent(&s);
413 assert_eq!(r, "line1\n\n line2");
414 }
415 }