]> git.lizzy.rs Git - rust.git/commitdiff
Add lint for doc without codeblocks
authorGuillaume Gomez <guillaume1.gomez@gmail.com>
Mon, 17 Sep 2018 22:25:50 +0000 (00:25 +0200)
committerGuillaume Gomez <guillaume1.gomez@gmail.com>
Tue, 9 Oct 2018 14:47:12 +0000 (16:47 +0200)
src/librustc/lint/builtin.rs
src/librustdoc/core.rs
src/librustdoc/html/markdown.rs
src/librustdoc/passes/collect_intra_doc_links.rs
src/librustdoc/test.rs

index 64056ece987706e566e5f539b2adc98a93f73571..66cb6f2b52acff46569999965cfa2b1577a4cf29 100644 (file)
     "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,
@@ -408,6 +414,7 @@ fn get_lints(&self) -> LintArray {
             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,
index b85604d860be4ca48b7e98f5faf1beb50a909d2e..fdd6929d98aedcb1b83a9187955ce1a8667d8ddb 100644 (file)
@@ -348,12 +348,14 @@ pub fn run_core(search_paths: SearchPaths,
     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());
 
index d14275aeb6bf5e52c50ecbecb0b7e81086cb37bd..22fa887c358145865fc03c5ece73b93eb2644472 100644 (file)
@@ -532,8 +532,10 @@ fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
     }
 }
 
-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;
index 7b2eb2259d6791bac9976858dd2b34d86f1c38ff..f97300357153b0f833372c57e931250ea773e8e2 100644 (file)
@@ -24,7 +24,8 @@
 
 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 =
@@ -211,6 +212,43 @@ fn resolve(&self,
     }
 }
 
+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() {
@@ -273,6 +311,12 @@ fn fold_item(&mut self, mut item: Item) -> Option<Item> {
         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('/') {
index dbebc3ab393977a0e226e20dabeb03607e0d757a..1a7a3f4478e741bc9509c9839f164947bace0363 100644 (file)
@@ -466,6 +466,14 @@ fn partition_source(s: &str) -> (String, String) {
     (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>,
 
@@ -534,7 +542,31 @@ fn generate_name(&self, line: usize, filename: &FileName) -> String {
         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();
@@ -588,7 +620,7 @@ pub fn add_test(&mut self, test: String, config: LangString, line: usize) {
         });
     }
 
-    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;
@@ -598,29 +630,7 @@ pub fn get_line(&self) -> 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())
-        }
-    }
-
-    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.