Skip to content

Commit 675319e

Browse files
lint if a private item has doctests
1 parent 6b9b97b commit 675319e

File tree

7 files changed

+158
-43
lines changed

7 files changed

+158
-43
lines changed

src/librustc/lint/builtin.rs

+7
Original file line numberDiff line numberDiff line change
@@ -318,6 +318,12 @@ declare_lint! {
318318
"warn about missing code example in an item's documentation"
319319
}
320320

321+
declare_lint! {
322+
pub PRIVATE_DOC_TESTS,
323+
Allow,
324+
"warn about doc test in private item"
325+
}
326+
321327
declare_lint! {
322328
pub WHERE_CLAUSES_OBJECT_SAFETY,
323329
Warn,
@@ -415,6 +421,7 @@ impl LintPass for HardwiredLints {
415421
DUPLICATE_MACRO_EXPORTS,
416422
INTRA_DOC_LINK_RESOLUTION_FAILURE,
417423
MISSING_DOC_CODE_EXAMPLES,
424+
PRIVATE_DOC_TESTS,
418425
WHERE_CLAUSES_OBJECT_SAFETY,
419426
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
420427
MACRO_USE_EXTERN_CRATE,

src/librustdoc/core.rs

+3-1
Original file line numberDiff line numberDiff line change
@@ -351,13 +351,15 @@ pub fn run_core(options: RustdocOptions) -> (clean::Crate, RenderInfo, RenderOpt
351351
let warnings_lint_name = lint::builtin::WARNINGS.name;
352352
let missing_docs = rustc_lint::builtin::MISSING_DOCS.name;
353353
let missing_doc_example = rustc_lint::builtin::MISSING_DOC_CODE_EXAMPLES.name;
354+
let private_doc_tests = rustc_lint::builtin::PRIVATE_DOC_TESTS.name;
354355

355356
// In addition to those specific lints, we also need to whitelist those given through
356357
// command line, otherwise they'll get ignored and we don't want that.
357358
let mut whitelisted_lints = vec![warnings_lint_name.to_owned(),
358359
intra_link_resolution_failure_name.to_owned(),
359360
missing_docs.to_owned(),
360-
missing_doc_example.to_owned()];
361+
missing_doc_example.to_owned(),
362+
private_doc_tests.to_owned()];
361363

362364
whitelisted_lints.extend(lint_opts.iter().map(|(lint, _)| lint).cloned());
363365

src/librustdoc/passes/collect_intra_doc_links.rs

+4-41
Original file line numberDiff line numberDiff line change
@@ -24,9 +24,9 @@ use std::ops::Range;
2424

2525
use core::DocContext;
2626
use fold::DocFolder;
27-
use html::markdown::{find_testable_code, markdown_links, ErrorCodes, LangString};
27+
use html::markdown::markdown_links;
2828

29-
use passes::Pass;
29+
use passes::{look_for_tests, Pass};
3030

3131
pub const COLLECT_INTRA_DOC_LINKS: Pass =
3232
Pass::early("collect-intra-doc-links", collect_intra_doc_links,
@@ -214,43 +214,6 @@ impl<'a, 'tcx, 'rcx, 'cstore> LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
214214
}
215215
}
216216

217-
fn look_for_tests<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx>(
218-
cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
219-
dox: &str,
220-
item: &Item,
221-
) {
222-
if (item.is_mod() && cx.tcx.hir.as_local_node_id(item.def_id).is_none()) ||
223-
cx.as_local_node_id(item.def_id).is_none() {
224-
// If non-local, no need to check anything.
225-
return;
226-
}
227-
228-
struct Tests {
229-
found_tests: usize,
230-
}
231-
232-
impl ::test::Tester for Tests {
233-
fn add_test(&mut self, _: String, _: LangString, _: usize) {
234-
self.found_tests += 1;
235-
}
236-
}
237-
238-
let mut tests = Tests {
239-
found_tests: 0,
240-
};
241-
242-
if find_testable_code(&dox, &mut tests, ErrorCodes::No).is_ok() {
243-
if tests.found_tests == 0 {
244-
let mut diag = cx.tcx.struct_span_lint_node(
245-
lint::builtin::MISSING_DOC_CODE_EXAMPLES,
246-
NodeId::from_u32(0),
247-
span_of_attrs(&item.attrs),
248-
"Missing code example in this documentation");
249-
diag.emit();
250-
}
251-
}
252-
}
253-
254217
impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
255218
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
256219
let item_node_id = if item.is_mod() {
@@ -313,7 +276,7 @@ impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstor
313276
let cx = self.cx;
314277
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
315278

316-
look_for_tests(&cx, &dox, &item);
279+
look_for_tests(&cx, &dox, &item, true);
317280

318281
if !self.is_nightly_build {
319282
return None;
@@ -488,7 +451,7 @@ fn macro_resolve(cx: &DocContext, path_str: &str) -> Option<Def> {
488451
None
489452
}
490453

491-
fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
454+
pub fn span_of_attrs(attrs: &Attributes) -> syntax_pos::Span {
492455
if attrs.doc_strings.is_empty() {
493456
return DUMMY_SP;
494457
}

src/librustdoc/passes/mod.rs

+59-1
Original file line numberDiff line numberDiff line change
@@ -12,16 +12,22 @@
1212
//! process.
1313
1414
use rustc::hir::def_id::DefId;
15+
use rustc::lint as lint;
1516
use rustc::middle::privacy::AccessLevels;
1617
use rustc::util::nodemap::DefIdSet;
1718
use std::mem;
1819
use std::fmt;
20+
use syntax::ast::NodeId;
1921

2022
use clean::{self, GetDefId, Item};
21-
use core::DocContext;
23+
use core::{DocContext, DocAccessLevels};
2224
use fold;
2325
use fold::StripItem;
2426

27+
use html::markdown::{find_testable_code, ErrorCodes, LangString};
28+
29+
use self::collect_intra_doc_links::span_of_attrs;
30+
2531
mod collapse_docs;
2632
pub use self::collapse_docs::COLLAPSE_DOCS;
2733

@@ -43,6 +49,9 @@ pub use self::propagate_doc_cfg::PROPAGATE_DOC_CFG;
4349
mod collect_intra_doc_links;
4450
pub use self::collect_intra_doc_links::COLLECT_INTRA_DOC_LINKS;
4551

52+
mod private_items_doc_tests;
53+
pub use self::private_items_doc_tests::CHECK_PRIVATE_ITEMS_DOC_TESTS;
54+
4655
mod collect_trait_impls;
4756
pub use self::collect_trait_impls::COLLECT_TRAIT_IMPLS;
4857

@@ -128,6 +137,7 @@ impl Pass {
128137

129138
/// The full list of passes.
130139
pub const PASSES: &'static [Pass] = &[
140+
CHECK_PRIVATE_ITEMS_DOC_TESTS,
131141
STRIP_HIDDEN,
132142
UNINDENT_COMMENTS,
133143
COLLAPSE_DOCS,
@@ -141,6 +151,7 @@ pub const PASSES: &'static [Pass] = &[
141151
/// The list of passes run by default.
142152
pub const DEFAULT_PASSES: &'static [&'static str] = &[
143153
"collect-trait-impls",
154+
"check-private-items-doc-tests",
144155
"strip-hidden",
145156
"strip-private",
146157
"collect-intra-doc-links",
@@ -152,6 +163,7 @@ pub const DEFAULT_PASSES: &'static [&'static str] = &[
152163
/// The list of default passes run with `--document-private-items` is passed to rustdoc.
153164
pub const DEFAULT_PRIVATE_PASSES: &'static [&'static str] = &[
154165
"collect-trait-impls",
166+
"check-private-items-doc-tests",
155167
"strip-priv-imports",
156168
"collect-intra-doc-links",
157169
"collapse-docs",
@@ -348,3 +360,49 @@ impl fold::DocFolder for ImportStripper {
348360
}
349361
}
350362
}
363+
364+
pub fn look_for_tests<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx>(
365+
cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
366+
dox: &str,
367+
item: &Item,
368+
check_missing_code: bool,
369+
) {
370+
if cx.as_local_node_id(item.def_id).is_none() {
371+
// If non-local, no need to check anything.
372+
return;
373+
}
374+
375+
struct Tests {
376+
found_tests: usize,
377+
}
378+
379+
impl ::test::Tester for Tests {
380+
fn add_test(&mut self, _: String, _: LangString, _: usize) {
381+
self.found_tests += 1;
382+
}
383+
}
384+
385+
let mut tests = Tests {
386+
found_tests: 0,
387+
};
388+
389+
if find_testable_code(&dox, &mut tests, ErrorCodes::No).is_ok() {
390+
if check_missing_code == true && tests.found_tests == 0 {
391+
let mut diag = cx.tcx.struct_span_lint_node(
392+
lint::builtin::MISSING_DOC_CODE_EXAMPLES,
393+
NodeId::from_u32(0),
394+
span_of_attrs(&item.attrs),
395+
"Missing code example in this documentation");
396+
diag.emit();
397+
} else if check_missing_code == false &&
398+
tests.found_tests > 0 &&
399+
!cx.renderinfo.borrow().access_levels.is_doc_reachable(item.def_id) {
400+
let mut diag = cx.tcx.struct_span_lint_node(
401+
lint::builtin::PRIVATE_DOC_TESTS,
402+
NodeId::from_u32(0),
403+
span_of_attrs(&item.attrs),
404+
"Documentation test in private item");
405+
diag.emit();
406+
}
407+
}
408+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
use clean::*;
12+
13+
use core::DocContext;
14+
use fold::DocFolder;
15+
16+
use passes::{look_for_tests, Pass};
17+
18+
pub const CHECK_PRIVATE_ITEMS_DOC_TESTS: Pass =
19+
Pass::early("check-private-items-doc-tests", check_private_items_doc_tests,
20+
"check private items doc tests");
21+
22+
struct PrivateItemDocTestLinter<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx> {
23+
cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
24+
}
25+
26+
impl<'a, 'tcx, 'rcx, 'cstore> PrivateItemDocTestLinter<'a, 'tcx, 'rcx, 'cstore> {
27+
fn new(cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>) -> Self {
28+
PrivateItemDocTestLinter {
29+
cx,
30+
}
31+
}
32+
}
33+
34+
pub fn check_private_items_doc_tests(krate: Crate, cx: &DocContext) -> Crate {
35+
let mut coll = PrivateItemDocTestLinter::new(cx);
36+
37+
coll.fold_crate(krate)
38+
}
39+
40+
impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for PrivateItemDocTestLinter<'a, 'tcx, 'rcx, 'cstore> {
41+
fn fold_item(&mut self, item: Item) -> Option<Item> {
42+
let cx = self.cx;
43+
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
44+
45+
look_for_tests(&cx, &dox, &item, false);
46+
47+
self.fold_item_recur(item)
48+
}
49+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
// Copyright 2018 The Rust Project Developers. See the COPYRIGHT
2+
// file at the top-level directory of this distribution and at
3+
// http://rust-lang.org/COPYRIGHT.
4+
//
5+
// Licensed under the Apache License, Version 2.0 <LICENSE-APACHE or
6+
// http://www.apache.org/licenses/LICENSE-2.0> or the MIT license
7+
// <LICENSE-MIT or http://opensource.org/licenses/MIT>, at your
8+
// option. This file may not be copied, modified, or distributed
9+
// except according to those terms.
10+
11+
#![deny(private_doc_tests)]
12+
13+
mod foo {
14+
/// private doc test
15+
///
16+
/// ```
17+
/// assert!(false);
18+
/// ```
19+
fn bar() {}
20+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
error: Documentation test in private item
2+
--> $DIR/private-item-doc-test.rs:14:5
3+
|
4+
LL | / /// private doc test
5+
LL | | ///
6+
LL | | /// ```
7+
LL | | /// assert!(false);
8+
LL | | /// ```
9+
| |___________^
10+
|
11+
note: lint level defined here
12+
--> $DIR/private-item-doc-test.rs:11:9
13+
|
14+
LL | #![deny(private_doc_tests)]
15+
| ^^^^^^^^^^^^^^^^^
16+

0 commit comments

Comments
 (0)