1 //! Contains information about "passes", used to modify crate information during the documentation
4 use rustc_hir::def_id::{DefId, DefIdSet};
5 use rustc_middle::middle::privacy::AccessLevels;
6 use rustc_span::{InnerSpan, Span, DUMMY_SP};
10 use self::Condition::*;
11 use crate::clean::{self, DocFragmentKind, GetDefId, Item};
12 use crate::core::DocContext;
13 use crate::fold::{DocFolder, StripItem};
16 pub use self::collapse_docs::COLLAPSE_DOCS;
19 pub use self::strip_hidden::STRIP_HIDDEN;
22 pub use self::strip_private::STRIP_PRIVATE;
24 mod strip_priv_imports;
25 pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS;
27 mod unindent_comments;
28 pub use self::unindent_comments::UNINDENT_COMMENTS;
30 mod propagate_doc_cfg;
31 pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
33 mod collect_intra_doc_links;
34 pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
37 pub use self::doc_test_lints::CHECK_PRIVATE_ITEMS_DOC_TESTS;
39 mod collect_trait_impls;
40 pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
42 mod check_code_block_syntax;
43 pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX;
45 mod calculate_doc_coverage;
46 pub use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE;
49 pub use self::html_tags::CHECK_INVALID_HTML_TAGS;
51 /// A single pass over the cleaned documentation.
53 /// Runs in the compiler context, so it has access to types and traits and the like.
54 #[derive(Copy, Clone)]
56 pub name: &'static str,
57 pub run: fn(clean::Crate, &DocContext<'_>) -> clean::Crate,
58 pub description: &'static str,
61 /// In a list of passes, a pass that may or may not need to be run depending on options.
62 #[derive(Copy, Clone)]
63 pub struct ConditionalPass {
65 pub condition: Condition,
68 /// How to decide whether to run a conditional pass.
69 #[derive(Copy, Clone)]
72 /// When `--document-private-items` is passed.
74 /// When `--document-private-items` is not passed.
75 WhenNotDocumentPrivate,
76 /// When `--document-hidden-items` is not passed.
77 WhenNotDocumentHidden,
80 /// The full list of passes.
81 pub const PASSES: &[Pass] = &[
82 CHECK_PRIVATE_ITEMS_DOC_TESTS,
89 COLLECT_INTRA_DOC_LINKS,
90 CHECK_CODE_BLOCK_SYNTAX,
92 CALCULATE_DOC_COVERAGE,
93 CHECK_INVALID_HTML_TAGS,
96 /// The list of passes run by default.
97 pub const DEFAULT_PASSES: &[ConditionalPass] = &[
98 ConditionalPass::always(COLLECT_TRAIT_IMPLS),
99 ConditionalPass::always(COLLAPSE_DOCS),
100 ConditionalPass::always(UNINDENT_COMMENTS),
101 ConditionalPass::always(CHECK_PRIVATE_ITEMS_DOC_TESTS),
102 ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
103 ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
104 ConditionalPass::new(STRIP_PRIV_IMPORTS, WhenDocumentPrivate),
105 ConditionalPass::always(COLLECT_INTRA_DOC_LINKS),
106 ConditionalPass::always(CHECK_CODE_BLOCK_SYNTAX),
107 ConditionalPass::always(CHECK_INVALID_HTML_TAGS),
108 ConditionalPass::always(PROPAGATE_DOC_CFG),
111 /// The list of default passes run when `--doc-coverage` is passed to rustdoc.
112 pub const COVERAGE_PASSES: &[ConditionalPass] = &[
113 ConditionalPass::always(COLLECT_TRAIT_IMPLS),
114 ConditionalPass::new(STRIP_HIDDEN, WhenNotDocumentHidden),
115 ConditionalPass::new(STRIP_PRIVATE, WhenNotDocumentPrivate),
116 ConditionalPass::always(CALCULATE_DOC_COVERAGE),
119 impl ConditionalPass {
120 pub const fn always(pass: Pass) -> Self {
121 Self::new(pass, Always)
124 pub const fn new(pass: Pass, condition: Condition) -> Self {
125 ConditionalPass { pass, condition }
129 /// A shorthand way to refer to which set of passes to use, based on the presence of
130 /// `--no-defaults` and `--show-coverage`.
131 #[derive(Copy, Clone, PartialEq, Eq, Debug)]
132 pub enum DefaultPassOption {
138 /// Returns the given default set of passes.
139 pub fn defaults(default_set: DefaultPassOption) -> &'static [ConditionalPass] {
141 DefaultPassOption::Default => DEFAULT_PASSES,
142 DefaultPassOption::Coverage => COVERAGE_PASSES,
143 DefaultPassOption::None => &[],
147 /// If the given name matches a known pass, returns its information.
148 pub fn find_pass(pass_name: &str) -> Option<Pass> {
149 PASSES.iter().find(|p| p.name == pass_name).copied()
152 struct Stripper<'a> {
153 retained: &'a mut DefIdSet,
154 access_levels: &'a AccessLevels<DefId>,
155 update_retained: bool,
158 impl<'a> DocFolder for Stripper<'a> {
159 fn fold_item(&mut self, i: Item) -> Option<Item> {
161 clean::StrippedItem(..) => {
162 // We need to recurse into stripped modules to strip things
163 // like impl methods but when doing so we must not add any
164 // items to the `retained` set.
165 debug!("Stripper: recursing into stripped {:?} {:?}", i.type_(), i.name);
166 let old = mem::replace(&mut self.update_retained, false);
167 let ret = self.fold_item_recur(i);
168 self.update_retained = old;
171 // These items can all get re-exported
172 clean::OpaqueTyItem(..)
173 | clean::TypedefItem(..)
174 | clean::StaticItem(..)
175 | clean::StructItem(..)
176 | clean::EnumItem(..)
177 | clean::TraitItem(..)
178 | clean::FunctionItem(..)
179 | clean::VariantItem(..)
180 | clean::MethodItem(..)
181 | clean::ForeignFunctionItem(..)
182 | clean::ForeignStaticItem(..)
183 | clean::ConstantItem(..)
184 | clean::UnionItem(..)
185 | clean::AssocConstItem(..)
186 | clean::TraitAliasItem(..)
187 | clean::ForeignTypeItem => {
188 if i.def_id.is_local() {
189 if !self.access_levels.is_exported(i.def_id) {
190 debug!("Stripper: stripping {:?} {:?}", i.type_(), i.name);
196 clean::StructFieldItem(..) => {
197 if i.visibility != clean::Public {
198 return StripItem(i).strip();
202 clean::ModuleItem(..) => {
203 if i.def_id.is_local() && i.visibility != clean::Public {
204 debug!("Stripper: stripping module {:?}", i.name);
205 let old = mem::replace(&mut self.update_retained, false);
206 let ret = StripItem(self.fold_item_recur(i).unwrap()).strip();
207 self.update_retained = old;
212 // handled in the `strip-priv-imports` pass
213 clean::ExternCrateItem(..) | clean::ImportItem(..) => {}
215 clean::ImplItem(..) => {}
217 // tymethods/macros have no control over privacy
218 clean::MacroItem(..) | clean::TyMethodItem(..) => {}
220 // Proc-macros are always public
221 clean::ProcMacroItem(..) => {}
223 // Primitives are never stripped
224 clean::PrimitiveItem(..) => {}
226 // Associated types are never stripped
227 clean::AssocTypeItem(..) => {}
229 // Keywords are never stripped
230 clean::KeywordItem(..) => {}
233 let fastreturn = match i.inner {
234 // nothing left to do for traits (don't want to filter their
235 // methods out, visibility controlled by the trait)
236 clean::TraitItem(..) => true,
238 // implementations of traits are always public.
239 clean::ImplItem(ref imp) if imp.trait_.is_some() => true,
240 // Struct variant fields have inherited visibility
241 clean::VariantItem(clean::Variant { kind: clean::VariantKind::Struct(..) }) => true,
245 let i = if fastreturn {
246 if self.update_retained {
247 self.retained.insert(i.def_id);
251 self.fold_item_recur(i)
254 if let Some(ref i) = i {
255 if self.update_retained {
256 self.retained.insert(i.def_id);
263 // This stripper discards all impls which reference stripped items
264 struct ImplStripper<'a> {
265 retained: &'a DefIdSet,
268 impl<'a> DocFolder for ImplStripper<'a> {
269 fn fold_item(&mut self, i: Item) -> Option<Item> {
270 if let clean::ImplItem(ref imp) = i.inner {
271 // emptied none trait impls can be stripped
272 if imp.trait_.is_none() && imp.items.is_empty() {
275 if let Some(did) = imp.for_.def_id() {
276 if did.is_local() && !imp.for_.is_generic() && !self.retained.contains(&did) {
277 debug!("ImplStripper: impl item for stripped type; removing");
281 if let Some(did) = imp.trait_.def_id() {
282 if did.is_local() && !self.retained.contains(&did) {
283 debug!("ImplStripper: impl item for stripped trait; removing");
287 if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) {
288 for typaram in generics {
289 if let Some(did) = typaram.def_id() {
290 if did.is_local() && !self.retained.contains(&did) {
292 "ImplStripper: stripped item in trait's generics; removing impl"
300 self.fold_item_recur(i)
304 // This stripper discards all private import statements (`use`, `extern crate`)
305 struct ImportStripper;
306 impl DocFolder for ImportStripper {
307 fn fold_item(&mut self, i: Item) -> Option<Item> {
309 clean::ExternCrateItem(..) | clean::ImportItem(..) if i.visibility != clean::Public => {
312 _ => self.fold_item_recur(i),
317 /// Returns a span encompassing all the given attributes.
318 crate fn span_of_attrs(attrs: &clean::Attributes) -> Option<Span> {
319 if attrs.doc_strings.is_empty() {
322 let start = attrs.doc_strings[0].span;
323 if start == DUMMY_SP {
326 let end = attrs.doc_strings.last().expect("no doc strings provided").span;
330 /// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code.
332 /// This method will return `None` if we cannot construct a span from the source map or if the
333 /// attributes are not all sugared doc comments. It's difficult to calculate the correct span in
334 /// that case due to escaping and other source features.
335 crate fn source_span_for_markdown_range(
338 md_range: &Range<usize>,
339 attrs: &clean::Attributes,
341 let is_all_sugared_doc =
342 attrs.doc_strings.iter().all(|frag| frag.kind == DocFragmentKind::SugaredDoc);
344 if !is_all_sugared_doc {
348 let snippet = cx.sess().source_map().span_to_snippet(span_of_attrs(attrs)?).ok()?;
350 let starting_line = markdown[..md_range.start].matches('\n').count();
351 let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count();
353 // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat
354 // CRLF and LF line endings the same way.
355 let mut src_lines = snippet.split_terminator('\n');
356 let md_lines = markdown.split_terminator('\n');
358 // The number of bytes from the source span to the markdown span that are not part
359 // of the markdown, like comment markers.
360 let mut start_bytes = 0;
361 let mut end_bytes = 0;
363 'outer: for (line_no, md_line) in md_lines.enumerate() {
365 let source_line = src_lines.next().expect("could not find markdown in source");
366 match source_line.find(md_line) {
368 if line_no == starting_line {
369 start_bytes += offset;
371 if starting_line == ending_line {
374 } else if line_no == ending_line {
377 } else if line_no < starting_line {
378 start_bytes += source_line.len() - md_line.len();
380 end_bytes += source_line.len() - md_line.len();
385 // Since this is a source line that doesn't include a markdown line,
386 // we have to count the newline that we split from earlier.
387 if line_no <= starting_line {
388 start_bytes += source_line.len() + 1;
390 end_bytes += source_line.len() + 1;
397 Some(span_of_attrs(attrs)?.from_inner(InnerSpan::new(
398 md_range.start + start_bytes,
399 md_range.end + start_bytes + end_bytes,