1 //! Contains information about "passes", used to modify crate information during the documentation
4 use rustc::hir::def_id::DefId;
5 use rustc::lint as lint;
6 use rustc::middle::privacy::AccessLevels;
7 use rustc::util::nodemap::DefIdSet;
10 use syntax::ast::NodeId;
11 use syntax_pos::{DUMMY_SP, Span};
14 use clean::{self, GetDefId, Item};
15 use core::{DocContext, DocAccessLevels};
19 use html::markdown::{find_testable_code, ErrorCodes, LangString};
22 pub use self::collapse_docs::COLLAPSE_DOCS;
25 pub use self::strip_hidden::STRIP_HIDDEN;
28 pub use self::strip_private::STRIP_PRIVATE;
30 mod strip_priv_imports;
31 pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
33 mod unindent_comments;
34 pub use self::unindent_comments::UNINDENT_COMMENTS;
36 mod propagate_doc_cfg;
37 pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
39 mod collect_intra_doc_links;
40 pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
42 mod private_items_doc_tests;
43 pub use self::private_items_doc_tests::CHECK_PRIVATE_ITEMS_DOC_TESTS;
45 mod collect_trait_impls;
46 pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
48 mod check_code_block_syntax;
49 pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX;
51 /// Represents a single pass.
52 #[derive(Copy, Clone)]
54 /// An "early pass" is run in the compiler context, and can gather information about types and
55 /// traits and the like.
58 pass: fn(clean::Crate, &DocContext) -> clean::Crate,
59 description: &'static str,
61 /// A "late pass" is run between crate cleaning and page generation.
64 pass: fn(clean::Crate) -> clean::Crate,
65 description: &'static str,
69 impl fmt::Debug for Pass {
70 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
71 let mut dbg = match *self {
72 Pass::EarlyPass { .. } => f.debug_struct("EarlyPass"),
73 Pass::LatePass { .. } => f.debug_struct("LatePass"),
76 dbg.field("name", &self.name())
77 .field("pass", &"...")
78 .field("description", &self.description())
84 /// Constructs a new early pass.
85 pub const fn early(name: &'static str,
86 pass: fn(clean::Crate, &DocContext) -> clean::Crate,
87 description: &'static str) -> Pass {
88 Pass::EarlyPass { name, pass, description }
91 /// Constructs a new late pass.
92 pub const fn late(name: &'static str,
93 pass: fn(clean::Crate) -> clean::Crate,
94 description: &'static str) -> Pass {
95 Pass::LatePass { name, pass, description }
98 /// Returns the name of this pass.
99 pub fn name(self) -> &'static str {
101 Pass::EarlyPass { name, .. } |
102 Pass::LatePass { name, .. } => name,
106 /// Returns the description of this pass.
107 pub fn description(self) -> &'static str {
109 Pass::EarlyPass { description, .. } |
110 Pass::LatePass { description, .. } => description,
114 /// If this pass is an early pass, returns the pointer to its function.
115 pub fn early_fn(self) -> Option<fn(clean::Crate, &DocContext) -> clean::Crate> {
117 Pass::EarlyPass { pass, .. } => Some(pass),
122 /// If this pass is a late pass, returns the pointer to its function.
123 pub fn late_fn(self) -> Option<fn(clean::Crate) -> clean::Crate> {
125 Pass::LatePass { pass, .. } => Some(pass),
131 /// The full list of passes.
132 pub const PASSES: &'static [Pass] = &[
133 CHECK_PRIVATE_ITEMS_DOC_TESTS,
140 COLLECT_INTRA_DOC_LINKS,
141 CHECK_CODE_BLOCK_SYNTAX,
145 /// The list of passes run by default.
146 pub const DEFAULT_PASSES: &'static [&'static str] = &[
147 "collect-trait-impls",
148 "check-private-items-doc-tests",
151 "collect-intra-doc-links",
152 "check-code-block-syntax",
158 /// The list of default passes run with `--document-private-items` is passed to rustdoc.
159 pub const DEFAULT_PRIVATE_PASSES: &'static [&'static str] = &[
160 "collect-trait-impls",
161 "check-private-items-doc-tests",
162 "strip-priv-imports",
163 "collect-intra-doc-links",
164 "check-code-block-syntax",
170 /// A shorthand way to refer to which set of passes to use, based on the presence of
171 /// `--no-defaults` or `--document-private-items`.
172 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
173 pub enum DefaultPassOption {
179 /// Returns the given default set of passes.
180 pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] {
182 DefaultPassOption::Default => DEFAULT_PASSES,
183 DefaultPassOption::Private => DEFAULT_PRIVATE_PASSES,
184 DefaultPassOption::None => &[],
188 /// If the given name matches a known pass, returns its information.
189 pub fn find_pass(pass_name: &str) -> Option<Pass> {
190 PASSES.iter().find(|p| p.name() == pass_name).cloned()
193 struct Stripper<'a> {
194 retained: &'a mut DefIdSet,
195 access_levels: &'a AccessLevels<DefId>,
196 update_retained: bool,
199 impl<'a> fold::DocFolder for Stripper<'a> {
200 fn fold_item(&mut self, i: Item) -> Option<Item> {
202 clean::StrippedItem(..) => {
203 // We need to recurse into stripped modules to strip things
204 // like impl methods but when doing so we must not add any
205 // items to the `retained` set.
206 debug!("Stripper: recursing into stripped {} {:?}", i.type_(), i.name);
207 let old = mem::replace(&mut self.update_retained, false);
208 let ret = self.fold_item_recur(i);
209 self.update_retained = old;
212 // These items can all get re-exported
213 clean::ExistentialItem(..)
214 | clean::TypedefItem(..)
215 | clean::StaticItem(..)
216 | clean::StructItem(..)
217 | clean::EnumItem(..)
218 | clean::TraitItem(..)
219 | clean::FunctionItem(..)
220 | clean::VariantItem(..)
221 | clean::MethodItem(..)
222 | clean::ForeignFunctionItem(..)
223 | clean::ForeignStaticItem(..)
224 | clean::ConstantItem(..)
225 | clean::UnionItem(..)
226 | clean::AssociatedConstItem(..)
227 | clean::TraitAliasItem(..)
228 | clean::ForeignTypeItem => {
229 if i.def_id.is_local() {
230 if !self.access_levels.is_exported(i.def_id) {
231 debug!("Stripper: stripping {} {:?}", i.type_(), i.name);
237 clean::StructFieldItem(..) => {
238 if i.visibility != Some(clean::Public) {
239 return StripItem(i).strip();
243 clean::ModuleItem(..) => {
244 if i.def_id.is_local() && i.visibility != Some(clean::Public) {
245 debug!("Stripper: stripping module {:?}", i.name);
246 let old = mem::replace(&mut self.update_retained, false);
247 let ret = StripItem(self.fold_item_recur(i).unwrap()).strip();
248 self.update_retained = old;
253 // handled in the `strip-priv-imports` pass
254 clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
256 clean::ImplItem(..) => {}
258 // tymethods/macros have no control over privacy
259 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
261 // Proc-macros are always public
262 clean::ProcMacroItem(..) => {}
264 // Primitives are never stripped
265 clean::PrimitiveItem(..) => {}
267 // Associated types are never stripped
268 clean::AssociatedTypeItem(..) => {}
270 // Keywords are never stripped
271 clean::KeywordItem(..) => {}
274 let fastreturn = match i.inner {
275 // nothing left to do for traits (don't want to filter their
276 // methods out, visibility controlled by the trait)
277 clean::TraitItem(..) => true,
279 // implementations of traits are always public.
280 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
281 // Struct variant fields have inherited visibility
282 clean::VariantItem(clean::Variant {
283 kind: clean::VariantKind::Struct(..),
288 let i = if fastreturn {
289 if self.update_retained {
290 self.retained.insert(i.def_id);
294 self.fold_item_recur(i)
297 if let Some(ref i) = i {
298 if self.update_retained {
299 self.retained.insert(i.def_id);
306 // This stripper discards all impls which reference stripped items
307 struct ImplStripper<'a> {
308 retained: &'a DefIdSet,
311 impl<'a> fold::DocFolder for ImplStripper<'a> {
312 fn fold_item(&mut self, i: Item) -> Option<Item> {
313 if let clean::ImplItem(ref imp) = i.inner {
314 // emptied none trait impls can be stripped
315 if imp.trait_.is_none() && imp.items.is_empty() {
318 if let Some(did) = imp.for_.def_id() {
319 if did.is_local() && !imp.for_.is_generic() && !self.retained.contains(&did) {
320 debug!("ImplStripper: impl item for stripped type; removing");
324 if let Some(did) = imp.trait_.def_id() {
325 if did.is_local() && !self.retained.contains(&did) {
326 debug!("ImplStripper: impl item for stripped trait; removing");
330 if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
331 for typaram in generics {
332 if let Some(did) = typaram.def_id() {
333 if did.is_local() && !self.retained.contains(&did) {
334 debug!("ImplStripper: stripped item in trait's generics; \
342 self.fold_item_recur(i)
346 // This stripper discards all private import statements (`use`, `extern crate`)
347 struct ImportStripper;
348 impl fold::DocFolder for ImportStripper {
349 fn fold_item(&mut self, i: Item) -> Option<Item> {
351 clean::ExternCrateItem(..) | clean::ImportItem(..)
352 if i.visibility != Some(clean::Public) =>
356 _ => self.fold_item_recur(i),
361 pub fn look_for_tests<'a, 'tcx: 'a, 'rcx: 'a>(
362 cx: &'a DocContext<'a, 'tcx, 'rcx>,
365 check_missing_code: bool,
367 if cx.as_local_node_id(item.def_id).is_none() {
368 // If non-local, no need to check anything.
376 impl ::test::Tester for Tests {
377 fn add_test(&mut self, _: String, _: LangString, _: usize) {
378 self.found_tests += 1;
382 let mut tests = Tests {
386 if find_testable_code(&dox, &mut tests, ErrorCodes::No).is_ok() {
387 if check_missing_code == true && tests.found_tests == 0 {
388 let mut diag = cx.tcx.struct_span_lint_node(
389 lint::builtin::MISSING_DOC_CODE_EXAMPLES,
391 span_of_attrs(&item.attrs),
392 "Missing code example in this documentation");
394 } else if check_missing_code == false &&
395 tests.found_tests > 0 &&
396 !cx.renderinfo.borrow().access_levels.is_doc_reachable(item.def_id) {
397 let mut diag = cx.tcx.struct_span_lint_node(
398 lint::builtin::PRIVATE_DOC_TESTS,
400 span_of_attrs(&item.attrs),
401 "Documentation test in private item");
407 /// Returns a span encompassing all the given attributes.
408 crate fn span_of_attrs(attrs: &clean::Attributes) -> Span {
409 if attrs.doc_strings.is_empty() {
412 let start = attrs.doc_strings[0].span();
413 let end = attrs.doc_strings.last().expect("No doc strings provided").span();
417 /// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
419 /// This method will return `None` if we cannot construct a span from the source map or if the
420 /// attributes are not all sugared doc comments. It's difficult to calculate the correct span in
421 /// that case due to escaping and other source features.
422 crate fn source_span_for_markdown_range(
425 md_range: &Range<usize>,
426 attrs: &clean::Attributes,
428 let is_all_sugared_doc = attrs.doc_strings.iter().all(|frag| match frag {
429 clean::DocFragment::SugaredDoc(..) => true,
433 if !is_all_sugared_doc {
440 .span_to_snippet(span_of_attrs(attrs))
443 let starting_line = markdown[..md_range.start].lines().count() - 1;
444 let ending_line = markdown[..md_range.end].lines().count() - 1;
446 // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we only
447 // we can treat CRLF and LF line endings the same way.
448 let mut src_lines = snippet.split_terminator('\n');
449 let md_lines = markdown.split_terminator('\n');
451 // The number of bytes from the source span to the markdown span that are not part
452 // of the markdown, like comment markers.
453 let mut start_bytes = 0;
454 let mut end_bytes = 0;
456 'outer: for (line_no, md_line) in md_lines.enumerate() {
458 let source_line = src_lines.next().expect("could not find markdown in source");
459 match source_line.find(md_line) {
461 if line_no == starting_line {
462 start_bytes += offset;
464 if starting_line == ending_line {
467 } else if line_no == ending_line {
470 } else if line_no < starting_line {
471 start_bytes += source_line.len() - md_line.len();
473 end_bytes += source_line.len() - md_line.len();
478 // Since this is a source line that doesn't include a markdown line,
479 // we have to count the newline that we split from earlier.
480 if line_no <= starting_line {
481 start_bytes += source_line.len() + 1;
483 end_bytes += source_line.len() + 1;
490 let sp = span_of_attrs(attrs).from_inner_byte_pos(
491 md_range.start + start_bytes,
492 md_range.end + start_bytes + end_bytes,