]>
Commit | Line | Data |
---|---|---|
3dfed10e XL |
1 | //! This pass is overloaded and runs two different lints. |
2 | //! | |
5869c6ff XL |
3 | //! - MISSING_DOC_CODE_EXAMPLES: this lint is **UNSTABLE** and looks for public items missing doctests |
4 | //! - PRIVATE_DOC_TESTS: this lint is **STABLE** and looks for private items with doctests. | |
3dfed10e | 5 | |
cdc7bbd5 | 6 | use super::Pass; |
3dfed10e XL |
7 | use crate::clean; |
8 | use crate::clean::*; | |
9 | use crate::core::DocContext; | |
10 | use crate::fold::DocFolder; | |
1b1a35ee | 11 | use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString}; |
6a06907d | 12 | use crate::visit_ast::inherits_doc_hidden; |
fc512014 | 13 | use rustc_middle::lint::LintLevelSource; |
3dfed10e | 14 | use rustc_session::lint; |
6a06907d | 15 | use rustc_span::symbol::sym; |
3dfed10e | 16 | |
fc512014 | 17 | crate const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass = Pass { |
3dfed10e XL |
18 | name: "check-private-items-doc-tests", |
19 | run: check_private_items_doc_tests, | |
20 | description: "check private items doc tests", | |
21 | }; | |
22 | ||
23 | struct PrivateItemDocTestLinter<'a, 'tcx> { | |
6a06907d | 24 | cx: &'a mut DocContext<'tcx>, |
3dfed10e XL |
25 | } |
26 | ||
6a06907d XL |
27 | crate fn check_private_items_doc_tests(krate: Crate, cx: &mut DocContext<'_>) -> Crate { |
28 | let mut coll = PrivateItemDocTestLinter { cx }; | |
3dfed10e XL |
29 | |
30 | coll.fold_crate(krate) | |
31 | } | |
32 | ||
33 | impl<'a, 'tcx> DocFolder for PrivateItemDocTestLinter<'a, 'tcx> { | |
34 | fn fold_item(&mut self, item: Item) -> Option<Item> { | |
3dfed10e XL |
35 | let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new); |
36 | ||
6a06907d | 37 | look_for_tests(self.cx, &dox, &item); |
3dfed10e | 38 | |
fc512014 | 39 | Some(self.fold_item_recur(item)) |
3dfed10e XL |
40 | } |
41 | } | |
42 | ||
43 | pub(crate) struct Tests { | |
44 | pub(crate) found_tests: usize, | |
45 | } | |
46 | ||
1b1a35ee XL |
47 | impl crate::doctest::Tester for Tests { |
48 | fn add_test(&mut self, _: String, config: LangString, _: usize) { | |
49 | if config.rust && config.ignore == Ignore::None { | |
50 | self.found_tests += 1; | |
51 | } | |
3dfed10e XL |
52 | } |
53 | } | |
54 | ||
fc512014 | 55 | crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool { |
136023e0 | 56 | if !cx.cache.access_levels.is_public(item.def_id.expect_def_id()) |
6a06907d XL |
57 | || matches!( |
58 | *item.kind, | |
59 | clean::StructFieldItem(_) | |
60 | | clean::VariantItem(_) | |
61 | | clean::AssocConstItem(_, _) | |
62 | | clean::AssocTypeItem(_, _) | |
63 | | clean::TypedefItem(_, _) | |
64 | | clean::StaticItem(_) | |
65 | | clean::ConstantItem(_) | |
66 | | clean::ExternCrateItem { .. } | |
67 | | clean::ImportItem(_) | |
68 | | clean::PrimitiveItem(_) | |
69 | | clean::KeywordItem(_) | |
70 | ) | |
71 | { | |
29967ef6 XL |
72 | return false; |
73 | } | |
136023e0 | 74 | // The `expect_def_id()` should be okay because `local_def_id_to_hir_id` |
17df50a5 | 75 | // would presumably panic if a fake `DefIndex` were passed. |
136023e0 | 76 | let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_id.expect_def_id().expect_local()); |
6a06907d XL |
77 | if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden) |
78 | || inherits_doc_hidden(cx.tcx, hir_id) | |
79 | { | |
80 | return false; | |
81 | } | |
82 | let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id); | |
fc512014 | 83 | level != lint::Level::Allow || matches!(source, LintLevelSource::Default) |
3dfed10e XL |
84 | } |
85 | ||
fc512014 | 86 | crate fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) { |
6a06907d | 87 | let hir_id = match DocContext::as_local_hir_id(cx.tcx, item.def_id) { |
3dfed10e XL |
88 | Some(hir_id) => hir_id, |
89 | None => { | |
90 | // If non-local, no need to check anything. | |
91 | return; | |
92 | } | |
93 | }; | |
94 | ||
1b1a35ee | 95 | let mut tests = Tests { found_tests: 0 }; |
3dfed10e XL |
96 | |
97 | find_testable_code(&dox, &mut tests, ErrorCodes::No, false, None); | |
98 | ||
fc512014 | 99 | if tests.found_tests == 0 && cx.tcx.sess.is_nightly_build() { |
29967ef6 | 100 | if should_have_doc_example(cx, &item) { |
3dfed10e | 101 | debug!("reporting error for {:?} (hir_id={:?})", item, hir_id); |
cdc7bbd5 | 102 | let sp = item.attr_span(cx.tcx); |
3dfed10e | 103 | cx.tcx.struct_span_lint_hir( |
6a06907d | 104 | crate::lint::MISSING_DOC_CODE_EXAMPLES, |
3dfed10e XL |
105 | hir_id, |
106 | sp, | |
107 | |lint| lint.build("missing code example in this documentation").emit(), | |
108 | ); | |
109 | } | |
136023e0 XL |
110 | } else if tests.found_tests > 0 |
111 | && !cx.cache.access_levels.is_public(item.def_id.expect_def_id()) | |
17df50a5 | 112 | { |
3dfed10e | 113 | cx.tcx.struct_span_lint_hir( |
6a06907d | 114 | crate::lint::PRIVATE_DOC_TESTS, |
3dfed10e | 115 | hir_id, |
cdc7bbd5 | 116 | item.attr_span(cx.tcx), |
3dfed10e XL |
117 | |lint| lint.build("documentation test in private item").emit(), |
118 | ); | |
119 | } | |
120 | } |