]>
Commit | Line | Data |
---|---|---|
b7449926 XL |
1 | //! Contains information about "passes", used to modify crate information during the documentation |
2 | //! process. | |
3 | ||
9e0c209e | 4 | use rustc::hir::def_id::DefId; |
a1dfa0c6 | 5 | use rustc::lint as lint; |
9e0c209e SL |
6 | use rustc::middle::privacy::AccessLevels; |
7 | use rustc::util::nodemap::DefIdSet; | |
8 | use std::mem; | |
dc9dc135 | 9 | use syntax_pos::{DUMMY_SP, InnerSpan, Span}; |
9fa01778 | 10 | use std::ops::Range; |
9e0c209e | 11 | |
9fa01778 XL |
12 | use crate::clean::{self, GetDefId, Item}; |
13 | use crate::core::{DocContext, DocAccessLevels}; | |
14 | use crate::fold::{DocFolder, StripItem}; | |
15 | use crate::html::markdown::{find_testable_code, ErrorCodes, LangString}; | |
a1dfa0c6 | 16 | |
9e0c209e | 17 | mod collapse_docs; |
b7449926 | 18 | pub use self::collapse_docs::COLLAPSE_DOCS; |
9e0c209e SL |
19 | |
20 | mod strip_hidden; | |
b7449926 | 21 | pub use self::strip_hidden::STRIP_HIDDEN; |
9e0c209e SL |
22 | |
23 | mod strip_private; | |
b7449926 | 24 | pub use self::strip_private::STRIP_PRIVATE; |
9e0c209e SL |
25 | |
26 | mod strip_priv_imports; | |
b7449926 | 27 | pub use self::strip_priv_imports::STRIP_PRIV_IMPORTS; |
9e0c209e SL |
28 | |
29 | mod unindent_comments; | |
b7449926 | 30 | pub use self::unindent_comments::UNINDENT_COMMENTS; |
9e0c209e | 31 | |
3b2f2976 | 32 | mod propagate_doc_cfg; |
b7449926 XL |
33 | pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG; |
34 | ||
35 | mod collect_intra_doc_links; | |
36 | pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS; | |
37 | ||
a1dfa0c6 XL |
38 | mod private_items_doc_tests; |
39 | pub use self::private_items_doc_tests::CHECK_PRIVATE_ITEMS_DOC_TESTS; | |
40 | ||
0bf4aa26 XL |
41 | mod collect_trait_impls; |
42 | pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS; | |
43 | ||
9fa01778 XL |
44 | mod check_code_block_syntax; |
45 | pub use self::check_code_block_syntax::CHECK_CODE_BLOCK_SYNTAX; | |
46 | ||
532ac7d7 XL |
47 | mod calculate_doc_coverage; |
48 | pub use self::calculate_doc_coverage::CALCULATE_DOC_COVERAGE; | |
b7449926 | 49 | |
532ac7d7 XL |
50 | /// A single pass over the cleaned documentation. |
51 | /// | |
52 | /// Runs in the compiler context, so it has access to types and traits and the like. | |
53 | #[derive(Copy, Clone)] | |
54 | pub struct Pass { | |
55 | pub name: &'static str, | |
56 | pub pass: fn(clean::Crate, &DocContext<'_>) -> clean::Crate, | |
57 | pub description: &'static str, | |
b7449926 XL |
58 | } |
59 | ||
60 | /// The full list of passes. | |
9e0c209e | 61 | pub const PASSES: &'static [Pass] = &[ |
a1dfa0c6 | 62 | CHECK_PRIVATE_ITEMS_DOC_TESTS, |
b7449926 XL |
63 | STRIP_HIDDEN, |
64 | UNINDENT_COMMENTS, | |
65 | COLLAPSE_DOCS, | |
66 | STRIP_PRIVATE, | |
67 | STRIP_PRIV_IMPORTS, | |
68 | PROPAGATE_DOC_CFG, | |
69 | COLLECT_INTRA_DOC_LINKS, | |
9fa01778 | 70 | CHECK_CODE_BLOCK_SYNTAX, |
0bf4aa26 | 71 | COLLECT_TRAIT_IMPLS, |
532ac7d7 | 72 | CALCULATE_DOC_COVERAGE, |
9e0c209e SL |
73 | ]; |
74 | ||
b7449926 | 75 | /// The list of passes run by default. |
532ac7d7 | 76 | pub const DEFAULT_PASSES: &[&str] = &[ |
0bf4aa26 | 77 | "collect-trait-impls", |
532ac7d7 XL |
78 | "collapse-docs", |
79 | "unindent-comments", | |
a1dfa0c6 | 80 | "check-private-items-doc-tests", |
9e0c209e SL |
81 | "strip-hidden", |
82 | "strip-private", | |
b7449926 | 83 | "collect-intra-doc-links", |
9fa01778 | 84 | "check-code-block-syntax", |
3b2f2976 | 85 | "propagate-doc-cfg", |
9e0c209e SL |
86 | ]; |
87 | ||
b7449926 | 88 | /// The list of default passes run with `--document-private-items` is passed to rustdoc. |
532ac7d7 | 89 | pub const DEFAULT_PRIVATE_PASSES: &[&str] = &[ |
0bf4aa26 | 90 | "collect-trait-impls", |
532ac7d7 XL |
91 | "collapse-docs", |
92 | "unindent-comments", | |
a1dfa0c6 | 93 | "check-private-items-doc-tests", |
8faf50e0 | 94 | "strip-priv-imports", |
b7449926 | 95 | "collect-intra-doc-links", |
9fa01778 | 96 | "check-code-block-syntax", |
8faf50e0 XL |
97 | "propagate-doc-cfg", |
98 | ]; | |
99 | ||
532ac7d7 XL |
100 | /// The list of default passes run when `--doc-coverage` is passed to rustdoc. |
101 | pub const DEFAULT_COVERAGE_PASSES: &'static [&'static str] = &[ | |
102 | "collect-trait-impls", | |
103 | "strip-hidden", | |
104 | "strip-private", | |
105 | "calculate-doc-coverage", | |
106 | ]; | |
107 | ||
108 | /// The list of default passes run when `--doc-coverage --document-private-items` is passed to | |
109 | /// rustdoc. | |
110 | pub const PRIVATE_COVERAGE_PASSES: &'static [&'static str] = &[ | |
111 | "collect-trait-impls", | |
112 | "calculate-doc-coverage", | |
113 | ]; | |
114 | ||
b7449926 XL |
115 | /// A shorthand way to refer to which set of passes to use, based on the presence of |
116 | /// `--no-defaults` or `--document-private-items`. | |
8faf50e0 XL |
117 | #[derive(Copy, Clone, PartialEq, Eq, Debug)] |
118 | pub enum DefaultPassOption { | |
119 | Default, | |
120 | Private, | |
532ac7d7 XL |
121 | Coverage, |
122 | PrivateCoverage, | |
8faf50e0 XL |
123 | None, |
124 | } | |
125 | ||
b7449926 | 126 | /// Returns the given default set of passes. |
8faf50e0 XL |
127 | pub fn defaults(default_set: DefaultPassOption) -> &'static [&'static str] { |
128 | match default_set { | |
129 | DefaultPassOption::Default => DEFAULT_PASSES, | |
130 | DefaultPassOption::Private => DEFAULT_PRIVATE_PASSES, | |
532ac7d7 XL |
131 | DefaultPassOption::Coverage => DEFAULT_COVERAGE_PASSES, |
132 | DefaultPassOption::PrivateCoverage => PRIVATE_COVERAGE_PASSES, | |
8faf50e0 XL |
133 | DefaultPassOption::None => &[], |
134 | } | |
135 | } | |
9e0c209e | 136 | |
b7449926 | 137 | /// If the given name matches a known pass, returns its information. |
532ac7d7 XL |
138 | pub fn find_pass(pass_name: &str) -> Option<&'static Pass> { |
139 | PASSES.iter().find(|p| p.name == pass_name) | |
b7449926 XL |
140 | } |
141 | ||
9e0c209e SL |
142 | struct Stripper<'a> { |
143 | retained: &'a mut DefIdSet, | |
144 | access_levels: &'a AccessLevels<DefId>, | |
145 | update_retained: bool, | |
146 | } | |
147 | ||
9fa01778 | 148 | impl<'a> DocFolder for Stripper<'a> { |
9e0c209e SL |
149 | fn fold_item(&mut self, i: Item) -> Option<Item> { |
150 | match i.inner { | |
151 | clean::StrippedItem(..) => { | |
152 | // We need to recurse into stripped modules to strip things | |
153 | // like impl methods but when doing so we must not add any | |
154 | // items to the `retained` set. | |
0bf4aa26 | 155 | debug!("Stripper: recursing into stripped {} {:?}", i.type_(), i.name); |
9e0c209e SL |
156 | let old = mem::replace(&mut self.update_retained, false); |
157 | let ret = self.fold_item_recur(i); | |
158 | self.update_retained = old; | |
159 | return ret; | |
160 | } | |
161 | // These items can all get re-exported | |
8faf50e0 XL |
162 | clean::ExistentialItem(..) |
163 | | clean::TypedefItem(..) | |
164 | | clean::StaticItem(..) | |
165 | | clean::StructItem(..) | |
166 | | clean::EnumItem(..) | |
167 | | clean::TraitItem(..) | |
168 | | clean::FunctionItem(..) | |
169 | | clean::VariantItem(..) | |
170 | | clean::MethodItem(..) | |
171 | | clean::ForeignFunctionItem(..) | |
172 | | clean::ForeignStaticItem(..) | |
173 | | clean::ConstantItem(..) | |
174 | | clean::UnionItem(..) | |
dc9dc135 | 175 | | clean::AssocConstItem(..) |
9fa01778 | 176 | | clean::TraitAliasItem(..) |
8faf50e0 | 177 | | clean::ForeignTypeItem => { |
9e0c209e SL |
178 | if i.def_id.is_local() { |
179 | if !self.access_levels.is_exported(i.def_id) { | |
0bf4aa26 | 180 | debug!("Stripper: stripping {} {:?}", i.type_(), i.name); |
9e0c209e SL |
181 | return None; |
182 | } | |
183 | } | |
184 | } | |
185 | ||
186 | clean::StructFieldItem(..) => { | |
187 | if i.visibility != Some(clean::Public) { | |
8faf50e0 | 188 | return StripItem(i).strip(); |
9e0c209e SL |
189 | } |
190 | } | |
191 | ||
192 | clean::ModuleItem(..) => { | |
193 | if i.def_id.is_local() && i.visibility != Some(clean::Public) { | |
0bf4aa26 | 194 | debug!("Stripper: stripping module {:?}", i.name); |
9e0c209e | 195 | let old = mem::replace(&mut self.update_retained, false); |
8faf50e0 | 196 | let ret = StripItem(self.fold_item_recur(i).unwrap()).strip(); |
9e0c209e SL |
197 | self.update_retained = old; |
198 | return ret; | |
199 | } | |
200 | } | |
201 | ||
202 | // handled in the `strip-priv-imports` pass | |
203 | clean::ExternCrateItem(..) | clean::ImportItem(..) => {} | |
204 | ||
2c00a5a8 | 205 | clean::ImplItem(..) => {} |
9e0c209e SL |
206 | |
207 | // tymethods/macros have no control over privacy | |
208 | clean::MacroItem(..) | clean::TyMethodItem(..) => {} | |
209 | ||
0bf4aa26 XL |
210 | // Proc-macros are always public |
211 | clean::ProcMacroItem(..) => {} | |
212 | ||
9e0c209e SL |
213 | // Primitives are never stripped |
214 | clean::PrimitiveItem(..) => {} | |
215 | ||
041b39d2 | 216 | // Associated types are never stripped |
dc9dc135 | 217 | clean::AssocTypeItem(..) => {} |
94b46f34 XL |
218 | |
219 | // Keywords are never stripped | |
220 | clean::KeywordItem(..) => {} | |
9e0c209e SL |
221 | } |
222 | ||
223 | let fastreturn = match i.inner { | |
224 | // nothing left to do for traits (don't want to filter their | |
225 | // methods out, visibility controlled by the trait) | |
226 | clean::TraitItem(..) => true, | |
227 | ||
228 | // implementations of traits are always public. | |
229 | clean::ImplItem(ref imp) if imp.trait_.is_some() => true, | |
230 | // Struct variant fields have inherited visibility | |
231 | clean::VariantItem(clean::Variant { | |
8faf50e0 | 232 | kind: clean::VariantKind::Struct(..), |
9e0c209e SL |
233 | }) => true, |
234 | _ => false, | |
235 | }; | |
236 | ||
237 | let i = if fastreturn { | |
238 | if self.update_retained { | |
239 | self.retained.insert(i.def_id); | |
240 | } | |
241 | return Some(i); | |
242 | } else { | |
243 | self.fold_item_recur(i) | |
244 | }; | |
245 | ||
041b39d2 XL |
246 | if let Some(ref i) = i { |
247 | if self.update_retained { | |
248 | self.retained.insert(i.def_id); | |
9e0c209e | 249 | } |
041b39d2 XL |
250 | } |
251 | i | |
9e0c209e SL |
252 | } |
253 | } | |
254 | ||
255 | // This stripper discards all impls which reference stripped items | |
256 | struct ImplStripper<'a> { | |
8faf50e0 | 257 | retained: &'a DefIdSet, |
9e0c209e SL |
258 | } |
259 | ||
9fa01778 | 260 | impl<'a> DocFolder for ImplStripper<'a> { |
9e0c209e SL |
261 | fn fold_item(&mut self, i: Item) -> Option<Item> { |
262 | if let clean::ImplItem(ref imp) = i.inner { | |
263 | // emptied none trait impls can be stripped | |
264 | if imp.trait_.is_none() && imp.items.is_empty() { | |
265 | return None; | |
266 | } | |
267 | if let Some(did) = imp.for_.def_id() { | |
8faf50e0 | 268 | if did.is_local() && !imp.for_.is_generic() && !self.retained.contains(&did) { |
0bf4aa26 | 269 | debug!("ImplStripper: impl item for stripped type; removing"); |
9e0c209e SL |
270 | return None; |
271 | } | |
272 | } | |
273 | if let Some(did) = imp.trait_.def_id() { | |
274 | if did.is_local() && !self.retained.contains(&did) { | |
0bf4aa26 | 275 | debug!("ImplStripper: impl item for stripped trait; removing"); |
9e0c209e SL |
276 | return None; |
277 | } | |
278 | } | |
ff7c6d11 XL |
279 | if let Some(generics) = imp.trait_.as_ref().and_then(|t| t.generics()) { |
280 | for typaram in generics { | |
281 | if let Some(did) = typaram.def_id() { | |
282 | if did.is_local() && !self.retained.contains(&did) { | |
0bf4aa26 XL |
283 | debug!("ImplStripper: stripped item in trait's generics; \ |
284 | removing impl"); | |
ff7c6d11 XL |
285 | return None; |
286 | } | |
287 | } | |
288 | } | |
289 | } | |
9e0c209e SL |
290 | } |
291 | self.fold_item_recur(i) | |
292 | } | |
293 | } | |
294 | ||
295 | // This stripper discards all private import statements (`use`, `extern crate`) | |
296 | struct ImportStripper; | |
9fa01778 | 297 | impl DocFolder for ImportStripper { |
9e0c209e SL |
298 | fn fold_item(&mut self, i: Item) -> Option<Item> { |
299 | match i.inner { | |
8faf50e0 XL |
300 | clean::ExternCrateItem(..) | clean::ImportItem(..) |
301 | if i.visibility != Some(clean::Public) => | |
302 | { | |
303 | None | |
304 | } | |
305 | _ => self.fold_item_recur(i), | |
9e0c209e SL |
306 | } |
307 | } | |
308 | } | |
a1dfa0c6 | 309 | |
532ac7d7 XL |
310 | pub fn look_for_tests<'tcx>( |
311 | cx: &DocContext<'tcx>, | |
a1dfa0c6 XL |
312 | dox: &str, |
313 | item: &Item, | |
314 | check_missing_code: bool, | |
315 | ) { | |
48663c56 XL |
316 | let hir_id = match cx.as_local_hir_id(item.def_id) { |
317 | Some(hir_id) => hir_id, | |
318 | None => { | |
319 | // If non-local, no need to check anything. | |
320 | return; | |
321 | } | |
322 | }; | |
a1dfa0c6 XL |
323 | |
324 | struct Tests { | |
325 | found_tests: usize, | |
326 | } | |
327 | ||
9fa01778 | 328 | impl crate::test::Tester for Tests { |
a1dfa0c6 XL |
329 | fn add_test(&mut self, _: String, _: LangString, _: usize) { |
330 | self.found_tests += 1; | |
331 | } | |
332 | } | |
333 | ||
334 | let mut tests = Tests { | |
335 | found_tests: 0, | |
336 | }; | |
337 | ||
48663c56 XL |
338 | find_testable_code(&dox, &mut tests, ErrorCodes::No); |
339 | ||
340 | if check_missing_code == true && tests.found_tests == 0 { | |
341 | let sp = span_of_attrs(&item.attrs).substitute_dummy(item.source.span()); | |
342 | let mut diag = cx.tcx.struct_span_lint_hir( | |
343 | lint::builtin::MISSING_DOC_CODE_EXAMPLES, | |
344 | hir_id, | |
345 | sp, | |
346 | "Missing code example in this documentation"); | |
347 | diag.emit(); | |
348 | } else if check_missing_code == false && | |
349 | tests.found_tests > 0 && | |
350 | !cx.renderinfo.borrow().access_levels.is_doc_reachable(item.def_id) { | |
351 | let mut diag = cx.tcx.struct_span_lint_hir( | |
352 | lint::builtin::PRIVATE_DOC_TESTS, | |
353 | hir_id, | |
354 | span_of_attrs(&item.attrs), | |
355 | "Documentation test in private item"); | |
356 | diag.emit(); | |
a1dfa0c6 XL |
357 | } |
358 | } | |
9fa01778 XL |
359 | |
360 | /// Returns a span encompassing all the given attributes. | |
361 | crate fn span_of_attrs(attrs: &clean::Attributes) -> Span { | |
362 | if attrs.doc_strings.is_empty() { | |
363 | return DUMMY_SP; | |
364 | } | |
365 | let start = attrs.doc_strings[0].span(); | |
366 | let end = attrs.doc_strings.last().expect("No doc strings provided").span(); | |
367 | start.to(end) | |
368 | } | |
369 | ||
370 | /// Attempts to match a range of bytes from parsed markdown to a `Span` in the source code. | |
371 | /// | |
372 | /// This method will return `None` if we cannot construct a span from the source map or if the | |
373 | /// attributes are not all sugared doc comments. It's difficult to calculate the correct span in | |
374 | /// that case due to escaping and other source features. | |
375 | crate fn source_span_for_markdown_range( | |
532ac7d7 | 376 | cx: &DocContext<'_>, |
9fa01778 XL |
377 | markdown: &str, |
378 | md_range: &Range<usize>, | |
379 | attrs: &clean::Attributes, | |
380 | ) -> Option<Span> { | |
381 | let is_all_sugared_doc = attrs.doc_strings.iter().all(|frag| match frag { | |
382 | clean::DocFragment::SugaredDoc(..) => true, | |
383 | _ => false, | |
384 | }); | |
385 | ||
386 | if !is_all_sugared_doc { | |
387 | return None; | |
388 | } | |
389 | ||
390 | let snippet = cx | |
391 | .sess() | |
392 | .source_map() | |
393 | .span_to_snippet(span_of_attrs(attrs)) | |
394 | .ok()?; | |
395 | ||
532ac7d7 XL |
396 | let starting_line = markdown[..md_range.start].matches('\n').count(); |
397 | let ending_line = starting_line + markdown[md_range.start..md_range.end].matches('\n').count(); | |
9fa01778 | 398 | |
532ac7d7 XL |
399 | // We use `split_terminator('\n')` instead of `lines()` when counting bytes so that we treat |
400 | // CRLF and LF line endings the same way. | |
9fa01778 XL |
401 | let mut src_lines = snippet.split_terminator('\n'); |
402 | let md_lines = markdown.split_terminator('\n'); | |
403 | ||
404 | // The number of bytes from the source span to the markdown span that are not part | |
405 | // of the markdown, like comment markers. | |
406 | let mut start_bytes = 0; | |
407 | let mut end_bytes = 0; | |
408 | ||
409 | 'outer: for (line_no, md_line) in md_lines.enumerate() { | |
410 | loop { | |
411 | let source_line = src_lines.next().expect("could not find markdown in source"); | |
412 | match source_line.find(md_line) { | |
413 | Some(offset) => { | |
414 | if line_no == starting_line { | |
415 | start_bytes += offset; | |
416 | ||
417 | if starting_line == ending_line { | |
418 | break 'outer; | |
419 | } | |
420 | } else if line_no == ending_line { | |
421 | end_bytes += offset; | |
422 | break 'outer; | |
423 | } else if line_no < starting_line { | |
424 | start_bytes += source_line.len() - md_line.len(); | |
425 | } else { | |
426 | end_bytes += source_line.len() - md_line.len(); | |
427 | } | |
428 | break; | |
429 | } | |
430 | None => { | |
431 | // Since this is a source line that doesn't include a markdown line, | |
432 | // we have to count the newline that we split from earlier. | |
433 | if line_no <= starting_line { | |
434 | start_bytes += source_line.len() + 1; | |
435 | } else { | |
436 | end_bytes += source_line.len() + 1; | |
437 | } | |
438 | } | |
439 | } | |
440 | } | |
441 | } | |
442 | ||
dc9dc135 | 443 | let sp = span_of_attrs(attrs).from_inner(InnerSpan::new( |
9fa01778 XL |
444 | md_range.start + start_bytes, |
445 | md_range.end + start_bytes + end_bytes, | |
dc9dc135 | 446 | )); |
9fa01778 XL |
447 | |
448 | Some(sp) | |
449 | } |