]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/passes/check_doc_test_visibility.rs
New upstream version 1.67.1+dfsg1
[rustc.git] / src / librustdoc / passes / check_doc_test_visibility.rs
CommitLineData
a2a8927a
XL
1//! Looks for items missing (or incorrectly having) doctests.
2//!
3dfed10e
XL
3//! This pass is overloaded and runs two different lints.
4//!
c295e0f8 5//! - MISSING_DOC_CODE_EXAMPLES: this lint is **UNSTABLE** and looks for public items missing doctests.
5869c6ff 6//! - PRIVATE_DOC_TESTS: this lint is **STABLE** and looks for private items with doctests.
3dfed10e 7
cdc7bbd5 8use super::Pass;
3dfed10e
XL
9use crate::clean;
10use crate::clean::*;
11use crate::core::DocContext;
1b1a35ee 12use crate::html::markdown::{find_testable_code, ErrorCodes, Ignore, LangString};
3c0e092e 13use crate::visit::DocVisitor;
6a06907d 14use crate::visit_ast::inherits_doc_hidden;
c295e0f8 15use rustc_hir as hir;
fc512014 16use rustc_middle::lint::LintLevelSource;
3dfed10e 17use rustc_session::lint;
6a06907d 18use rustc_span::symbol::sym;
3dfed10e 19
923072b8 20pub(crate) const CHECK_DOC_TEST_VISIBILITY: Pass = Pass {
c295e0f8
XL
21 name: "check_doc_test_visibility",
22 run: check_doc_test_visibility,
23 description: "run various visibility-related lints on doctests",
3dfed10e
XL
24};
25
c295e0f8 26struct DocTestVisibilityLinter<'a, 'tcx> {
6a06907d 27 cx: &'a mut DocContext<'tcx>,
3dfed10e
XL
28}
29
923072b8 30pub(crate) fn check_doc_test_visibility(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
c295e0f8 31 let mut coll = DocTestVisibilityLinter { cx };
3c0e092e
XL
32 coll.visit_crate(&krate);
33 krate
3dfed10e
XL
34}
35
3c0e092e
XL
36impl<'a, 'tcx> DocVisitor for DocTestVisibilityLinter<'a, 'tcx> {
37 fn visit_item(&mut self, item: &Item) {
923072b8 38 let dox = item.attrs.collapsed_doc_value().unwrap_or_default();
3dfed10e 39
923072b8 40 look_for_tests(self.cx, &dox, item);
3dfed10e 41
3c0e092e 42 self.visit_item_recur(item)
3dfed10e
XL
43 }
44}
45
46pub(crate) struct Tests {
47 pub(crate) found_tests: usize,
48}
49
1b1a35ee
XL
50impl crate::doctest::Tester for Tests {
51 fn add_test(&mut self, _: String, config: LangString, _: usize) {
52 if config.rust && config.ignore == Ignore::None {
53 self.found_tests += 1;
54 }
3dfed10e
XL
55 }
56}
57
923072b8 58pub(crate) fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
487cf647 59 if !cx.cache.effective_visibilities.is_directly_public(cx.tcx, item.item_id.expect_def_id())
6a06907d
XL
60 || matches!(
61 *item.kind,
62 clean::StructFieldItem(_)
63 | clean::VariantItem(_)
04454e1e 64 | clean::AssocConstItem(..)
5e7ed085 65 | clean::AssocTypeItem(..)
04454e1e 66 | clean::TypedefItem(_)
6a06907d
XL
67 | clean::StaticItem(_)
68 | clean::ConstantItem(_)
69 | clean::ExternCrateItem { .. }
70 | clean::ImportItem(_)
71 | clean::PrimitiveItem(_)
064997fb 72 | clean::KeywordItem
c295e0f8 73 // check for trait impl
064997fb 74 | clean::ImplItem(box clean::Impl { trait_: Some(_), .. })
6a06907d
XL
75 )
76 {
29967ef6
XL
77 return false;
78 }
c295e0f8 79
136023e0 80 // The `expect_def_id()` should be okay because `local_def_id_to_hir_id`
17df50a5 81 // would presumably panic if a fake `DefIndex` were passed.
04454e1e 82 let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.item_id.expect_def_id().expect_local());
c295e0f8
XL
83
84 // check if parent is trait impl
85 if let Some(parent_hir_id) = cx.tcx.hir().find_parent_node(hir_id) {
86 if let Some(parent_node) = cx.tcx.hir().find(parent_hir_id) {
87 if matches!(
88 parent_node,
89 hir::Node::Item(hir::Item {
90 kind: hir::ItemKind::Impl(hir::Impl { of_trait: Some(_), .. }),
91 ..
92 })
93 ) {
94 return false;
95 }
96 }
97 }
98
6a06907d
XL
99 if cx.tcx.hir().attrs(hir_id).lists(sym::doc).has_word(sym::hidden)
100 || inherits_doc_hidden(cx.tcx, hir_id)
c295e0f8 101 || cx.tcx.hir().span(hir_id).in_derive_expansion()
6a06907d
XL
102 {
103 return false;
104 }
105 let (level, source) = cx.tcx.lint_level_at_node(crate::lint::MISSING_DOC_CODE_EXAMPLES, hir_id);
fc512014 106 level != lint::Level::Allow || matches!(source, LintLevelSource::Default)
3dfed10e
XL
107}
108
923072b8 109pub(crate) fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
04454e1e 110 let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.item_id)
5e7ed085
FG
111 else {
112 // If non-local, no need to check anything.
113 return;
3dfed10e
XL
114 };
115
1b1a35ee 116 let mut tests = Tests { found_tests: 0 };
3dfed10e 117
3c0e092e 118 find_testable_code(dox, &mut tests, ErrorCodes::No, false, None);
3dfed10e 119
f2b60f7d 120 if tests.found_tests == 0 && cx.tcx.features().rustdoc_missing_doc_code_examples {
3c0e092e 121 if should_have_doc_example(cx, item) {
3dfed10e 122 debug!("reporting error for {:?} (hir_id={:?})", item, hir_id);
cdc7bbd5 123 let sp = item.attr_span(cx.tcx);
3dfed10e 124 cx.tcx.struct_span_lint_hir(
6a06907d 125 crate::lint::MISSING_DOC_CODE_EXAMPLES,
3dfed10e
XL
126 hir_id,
127 sp,
2b03887a
FG
128 "missing code example in this documentation",
129 |lint| lint,
3dfed10e
XL
130 );
131 }
136023e0 132 } else if tests.found_tests > 0
487cf647 133 && !cx.cache.effective_visibilities.is_exported(cx.tcx, item.item_id.expect_def_id())
17df50a5 134 {
3dfed10e 135 cx.tcx.struct_span_lint_hir(
6a06907d 136 crate::lint::PRIVATE_DOC_TESTS,
3dfed10e 137 hir_id,
cdc7bbd5 138 item.attr_span(cx.tcx),
2b03887a
FG
139 "documentation test in private item",
140 |lint| lint,
3dfed10e
XL
141 );
142 }
143}