]> git.proxmox.com Git - rustc.git/blame - src/librustdoc/passes/check_doc_test_visibility.rs
New upstream version 1.61.0+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
c295e0f8
XL
20crate const CHECK_DOC_TEST_VISIBILITY: Pass = Pass {
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
c295e0f8
XL
30crate fn check_doc_test_visibility(krate: Crate, cx: &mut DocContext<'_>) -> Crate {
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) {
3dfed10e
XL
38 let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
39
6a06907d 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
fc512014 58crate fn should_have_doc_example(cx: &DocContext<'_>, item: &clean::Item) -> bool {
136023e0 59 if !cx.cache.access_levels.is_public(item.def_id.expect_def_id())
6a06907d
XL
60 || matches!(
61 *item.kind,
62 clean::StructFieldItem(_)
63 | clean::VariantItem(_)
64 | clean::AssocConstItem(_, _)
5e7ed085 65 | clean::AssocTypeItem(..)
6a06907d
XL
66 | clean::TypedefItem(_, _)
67 | clean::StaticItem(_)
68 | clean::ConstantItem(_)
69 | clean::ExternCrateItem { .. }
70 | clean::ImportItem(_)
71 | clean::PrimitiveItem(_)
72 | clean::KeywordItem(_)
c295e0f8
XL
73 // check for trait impl
74 | clean::ImplItem(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.
136023e0 82 let hir_id = cx.tcx.hir().local_def_id_to_hir_id(item.def_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
fc512014 109crate fn look_for_tests<'tcx>(cx: &DocContext<'tcx>, dox: &str, item: &Item) {
5e7ed085
FG
110 let Some(hir_id) = DocContext::as_local_hir_id(cx.tcx, item.def_id)
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
fc512014 120 if tests.found_tests == 0 && cx.tcx.sess.is_nightly_build() {
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,
5e7ed085
FG
128 |lint| {
129 lint.build("missing code example in this documentation").emit();
130 },
3dfed10e
XL
131 );
132 }
136023e0 133 } else if tests.found_tests > 0
a2a8927a 134 && !cx.cache.access_levels.is_exported(item.def_id.expect_def_id())
17df50a5 135 {
3dfed10e 136 cx.tcx.struct_span_lint_hir(
6a06907d 137 crate::lint::PRIVATE_DOC_TESTS,
3dfed10e 138 hir_id,
cdc7bbd5 139 item.attr_span(cx.tcx),
5e7ed085
FG
140 |lint| {
141 lint.build("documentation test in private item").emit();
142 },
3dfed10e
XL
143 );
144 }
145}