"warn about documentation intra links resolution failure"
}
+declare_lint! {
+ pub MISSING_DOC_ITEM_CODE_EXAMPLE,
+ Allow,
+ "warn about missing code example in an item's documentation"
+}
+
declare_lint! {
pub WHERE_CLAUSES_OBJECT_SAFETY,
Warn,
DUPLICATE_ASSOCIATED_TYPE_BINDINGS,
DUPLICATE_MACRO_EXPORTS,
INTRA_DOC_LINK_RESOLUTION_FAILURE,
+ MISSING_DOC_CODE_EXAMPLES,
WHERE_CLAUSES_OBJECT_SAFETY,
PROC_MACRO_DERIVE_RESOLUTION_FALLBACK,
MACRO_USE_EXTERN_CRATE,
let intra_link_resolution_failure_name = lint::builtin::INTRA_DOC_LINK_RESOLUTION_FAILURE.name;
let warnings_lint_name = lint::builtin::WARNINGS.name;
let missing_docs = rustc_lint::builtin::MISSING_DOCS.name;
+ let missing_doc_example = rustc_lint::builtin::MISSING_DOC_ITEM_CODE_EXAMPLE.name;
// In addition to those specific lints, we also need to whitelist those given through
// command line, otherwise they'll get ignored and we don't want that.
let mut whitelisted_lints = vec![warnings_lint_name.to_owned(),
intra_link_resolution_failure_name.to_owned(),
- missing_docs.to_owned()];
+ missing_docs.to_owned(),
+ missing_doc_example.to_owned()];
whitelisted_lints.extend(cmd_lints.iter().map(|(lint, _)| lint).cloned());
}
}
-pub fn find_testable_code(
- doc: &str, tests: &mut test::Collector, error_codes: ErrorCodes,
+pub fn find_testable_code<T: test::Tester>(
+ doc: &str,
+ tests: &mut T,
+ error_codes: ErrorCodes,
) -> Result<(), TestableCodeError> {
let mut parser = Parser::new(doc);
let mut prev_offset = 0;
use core::DocContext;
use fold::DocFolder;
-use html::markdown::markdown_links;
+use html::markdown::{find_testable_code, markdown_links, ErrorCodes, LangString};
+
use passes::Pass;
pub const COLLECT_INTRA_DOC_LINKS: Pass =
}
}
+fn look_for_tests<'a, 'tcx: 'a, 'rcx: 'a, 'cstore: 'rcx>(
+ cx: &'a DocContext<'a, 'tcx, 'rcx, 'cstore>,
+ dox: &str,
+ item: &Item,
+) {
+ if (item.is_mod() && cx.tcx.hir.as_local_node_id(item.def_id).is_none()) ||
+ cx.as_local_node_id(item.def_id).is_none() {
+ // If non-local, no need to check anything.
+ return;
+ }
+
+ struct Tests {
+ found_tests: usize,
+ }
+
+ impl ::test::Tester for Tests {
+ fn add_test(&mut self, _: String, _: LangString, _: usize) {
+ self.found_tests += 1;
+ }
+ }
+
+ let mut tests = Tests {
+ found_tests: 0,
+ };
+
+ if find_testable_code(&dox, &mut tests, ErrorCodes::No).is_ok() {
+ if tests.found_tests == 0 {
+ let mut diag = cx.tcx.struct_span_lint_node(
+ lint::builtin::MISSING_DOC_ITEM_CODE_EXAMPLE,
+ NodeId::new(0),
+ span_of_attrs(&item.attrs),
+ "Missing code example in this documentation");
+ diag.emit();
+ }
+ }
+}
+
impl<'a, 'tcx, 'rcx, 'cstore> DocFolder for LinkCollector<'a, 'tcx, 'rcx, 'cstore> {
fn fold_item(&mut self, mut item: Item) -> Option<Item> {
let item_node_id = if item.is_mod() {
let cx = self.cx;
let dox = item.attrs.collapsed_doc_value().unwrap_or_else(String::new);
+ look_for_tests(&cx, &dox, &item);
+
+ if !UnstableFeatures::from_environment().is_nightly_build() {
+ return None;
+ }
+
for (ori_link, link_range) in markdown_links(&dox) {
// bail early for real links
if ori_link.contains('/') {
(before, after)
}
+pub trait Tester {
+ fn add_test(&mut self, test: String, config: LangString, line: usize);
+ fn get_line(&self) -> usize {
+ 0
+ }
+ fn register_header(&mut self, _name: &str, _level: u32) {}
+}
+
pub struct Collector {
pub tests: Vec<testing::TestDescAndFn>,
format!("{} - {} (line {})", filename, self.names.join("::"), line)
}
- pub fn add_test(&mut self, test: String, config: LangString, line: usize) {
+ pub fn set_position(&mut self, position: Span) {
+ self.position = position;
+ }
+
+ fn get_filename(&self) -> FileName {
+ if let Some(ref source_map) = self.source_map {
+ let filename = source_map.span_to_filename(self.position);
+ if let FileName::Real(ref filename) = filename {
+ if let Ok(cur_dir) = env::current_dir() {
+ if let Ok(path) = filename.strip_prefix(&cur_dir) {
+ return path.to_owned().into();
+ }
+ }
+ }
+ filename
+ } else if let Some(ref filename) = self.filename {
+ filename.clone().into()
+ } else {
+ FileName::Custom("input".to_owned())
+ }
+ }
+}
+
+impl Tester for Collector {
+ fn add_test(&mut self, test: String, config: LangString, line: usize) {
let filename = self.get_filename();
let name = self.generate_name(line, &filename);
let cfgs = self.cfgs.clone();
});
}
- pub fn get_line(&self) -> usize {
+ fn get_line(&self) -> usize {
if let Some(ref source_map) = self.source_map {
let line = self.position.lo().to_usize();
let line = source_map.lookup_char_pos(BytePos(line as u32)).line;
}
}
- pub fn set_position(&mut self, position: Span) {
- self.position = position;
- }
-
- fn get_filename(&self) -> FileName {
- if let Some(ref source_map) = self.source_map {
- let filename = source_map.span_to_filename(self.position);
- if let FileName::Real(ref filename) = filename {
- if let Ok(cur_dir) = env::current_dir() {
- if let Ok(path) = filename.strip_prefix(&cur_dir) {
- return path.to_owned().into();
- }
- }
- }
- filename
- } else if let Some(ref filename) = self.filename {
- filename.clone().into()
- } else {
- FileName::Custom("input".to_owned())
- }
- }
-
- pub fn register_header(&mut self, name: &str, level: u32) {
+ fn register_header(&mut self, name: &str, level: u32) {
if self.use_headers {
// we use these headings as test names, so it's good if
// they're valid identifiers.