diff --git a/compiler/rustc_lint_defs/src/lib.rs b/compiler/rustc_lint_defs/src/lib.rs index af9926400ca..aec0fc253ca 100644 --- a/compiler/rustc_lint_defs/src/lib.rs +++ b/compiler/rustc_lint_defs/src/lib.rs @@ -366,11 +366,25 @@ impl LintBuffer { /// ``` /// /// The `{{produces}}` tag will be automatically replaced with the output from -/// the example by the build system. You can build and view the rustc book -/// with `x.py doc --stage=1 src/doc/rustc --open`. If the lint example is too -/// complex to run as a simple example (for example, it needs an extern -/// crate), mark it with `ignore` and manually paste the expected output below -/// the example. +/// the example by the build system. If the lint example is too complex to run +/// as a simple example (for example, it needs an extern crate), mark the code +/// block with `ignore` and manually replace the `{{produces}}` line with the +/// expected output in a `text` code block. +/// +/// If this is a rustdoc-only lint, then only include a brief introduction +/// with a link with the text `[rustdoc book]` so that the validator knows +/// that this is for rustdoc only (see BROKEN_INTRA_DOC_LINKS as an example). +/// +/// Commands to view and test the documentation: +/// +/// * `./x.py doc --stage=1 src/doc/rustc --open`: Builds the rustc book and opens it. +/// * `./x.py test src/tools/lint-docs`: Validates that the lint docs have the +/// correct style, and that the code example actually emits the expected +/// lint. +/// +/// If you have already built the compiler, and you want to make changes to +/// just the doc comments, then use the `--keep-stage=0` flag with the above +/// commands to avoid rebuilding the compiler. #[macro_export] macro_rules! declare_lint { ($(#[$attr:meta])* $vis: vis $NAME: ident, $Level: ident, $desc: expr) => ( diff --git a/src/bootstrap/builder.rs b/src/bootstrap/builder.rs index 508d785834f..9336d7165ee 100644 --- a/src/bootstrap/builder.rs +++ b/src/bootstrap/builder.rs @@ -413,6 +413,7 @@ impl<'a> Builder<'a> { test::TheBook, test::UnstableBook, test::RustcBook, + test::LintDocs, test::RustcGuide, test::EmbeddedBook, test::EditionGuide, diff --git a/src/bootstrap/doc.rs b/src/bootstrap/doc.rs index af7f7eff894..bb0555c227d 100644 --- a/src/bootstrap/doc.rs +++ b/src/bootstrap/doc.rs @@ -726,6 +726,7 @@ fn symlink_dir_force(config: &Config, src: &Path, dst: &Path) -> io::Result<()> pub struct RustcBook { pub compiler: Compiler, pub target: TargetSelection, + pub validate: bool, } impl Step for RustcBook { @@ -742,6 +743,7 @@ impl Step for RustcBook { run.builder.ensure(RustcBook { compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.build), target: run.target, + validate: false, }); } @@ -772,6 +774,9 @@ impl Step for RustcBook { if builder.config.verbose() { cmd.arg("--verbose"); } + if self.validate { + cmd.arg("--validate"); + } // If the lib directories are in an unusual location (changed in // config.toml), then this needs to explicitly update the dylib search // path. diff --git a/src/bootstrap/test.rs b/src/bootstrap/test.rs index e087e2b8ff1..611fecca054 100644 --- a/src/bootstrap/test.rs +++ b/src/bootstrap/test.rs @@ -2115,3 +2115,36 @@ impl Step for TierCheck { try_run(builder, &mut cargo.into()); } } + +#[derive(Debug, Copy, Clone, PartialEq, Eq, Hash)] +pub struct LintDocs { + pub compiler: Compiler, + pub target: TargetSelection, +} + +impl Step for LintDocs { + type Output = (); + const DEFAULT: bool = true; + const ONLY_HOSTS: bool = true; + + fn should_run(run: ShouldRun<'_>) -> ShouldRun<'_> { + run.path("src/tools/lint-docs") + } + + fn make_run(run: RunConfig<'_>) { + run.builder.ensure(LintDocs { + compiler: run.builder.compiler(run.builder.top_stage, run.builder.config.build), + target: run.target, + }); + } + + /// Tests that the lint examples in the rustc book generate the correct + /// lints and have the expected format. + fn run(self, builder: &Builder<'_>) { + builder.ensure(crate::doc::RustcBook { + compiler: self.compiler, + target: self.target, + validate: true, + }); + } +} diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs index db667264d2f..0a69b18a332 100644 --- a/src/tools/lint-docs/src/groups.rs +++ b/src/tools/lint-docs/src/groups.rs @@ -5,6 +5,7 @@ use std::fmt::Write; use std::fs; use std::process::Command; +/// Descriptions of rustc lint groups. static GROUP_DESCRIPTIONS: &[(&str, &str)] = &[ ("unused", "Lints that detect things being declared but not used, or excess syntax"), ("rustdoc", "Rustdoc-specific lints"), @@ -86,17 +87,27 @@ impl<'a> LintExtractor<'a> { result.push_str("|-------|-------------|-------|\n"); result.push_str("| warnings | All lints that are set to issue warnings | See [warn-by-default] for the default set of warnings |\n"); for (group_name, group_lints) in groups { - let description = GROUP_DESCRIPTIONS - .iter() - .find(|(n, _)| n == group_name) - .ok_or_else(|| { - format!( + let description = match GROUP_DESCRIPTIONS.iter().find(|(n, _)| n == group_name) { + Some((_, desc)) => desc, + None if self.validate => { + return Err(format!( "lint group `{}` does not have a description, \ - please update the GROUP_DESCRIPTIONS list", + please update the GROUP_DESCRIPTIONS list in \ + src/tools/lint-docs/src/groups.rs", group_name ) - })? - .1; + .into()); + } + None => { + eprintln!( + "warning: lint group `{}` is missing from the GROUP_DESCRIPTIONS list\n\ + If this is a new lint group, please update the GROUP_DESCRIPTIONS in \ + src/tools/lint-docs/src/groups.rs", + group_name + ); + continue; + } + }; to_link.extend(group_lints); let brackets: Vec<_> = group_lints.iter().map(|l| format!("[{}]", l)).collect(); write!(result, "| {} | {} | {} |\n", group_name, description, brackets.join(", ")) diff --git a/src/tools/lint-docs/src/lib.rs b/src/tools/lint-docs/src/lib.rs index aafd33301ea..dc878b718ad 100644 --- a/src/tools/lint-docs/src/lib.rs +++ b/src/tools/lint-docs/src/lib.rs @@ -19,6 +19,8 @@ pub struct LintExtractor<'a> { pub rustc_target: &'a str, /// Verbose output. pub verbose: bool, + /// Validate the style and the code example. + pub validate: bool, } struct Lint { @@ -122,7 +124,7 @@ impl<'a> LintExtractor<'a> { let contents = fs::read_to_string(path) .map_err(|e| format!("could not read {}: {}", path.display(), e))?; let mut lines = contents.lines().enumerate(); - loop { + 'outer: loop { // Find a lint declaration. let lint_start = loop { match lines.next() { @@ -158,12 +160,22 @@ impl<'a> LintExtractor<'a> { ) })?; if doc_lines.is_empty() { - return Err(format!( - "did not find doc lines for lint `{}` in {}", - name, - path.display() - ) - .into()); + if self.validate { + return Err(format!( + "did not find doc lines for lint `{}` in {}", + name, + path.display() + ) + .into()); + } else { + eprintln!( + "warning: lint `{}` in {} does not define any doc lines, \ + these are required for the lint documentation", + name, + path.display() + ); + continue 'outer; + } } break (doc_lines, name); } @@ -234,13 +246,26 @@ impl<'a> LintExtractor<'a> { // Rustdoc lints are documented in the rustdoc book, don't check these. return Ok(()); } - lint.check_style()?; + if self.validate { + lint.check_style()?; + } // Unfortunately some lints have extra requirements that this simple test // setup can't handle (like extern crates). An alternative is to use a // separate test suite, and use an include mechanism such as mdbook's // `{{#rustdoc_include}}`. if !lint.is_ignored() { - self.replace_produces(lint)?; + if let Err(e) = self.replace_produces(lint) { + if self.validate { + return Err(e); + } + eprintln!( + "warning: the code example in lint `{}` in {} failed to \ + generate the expected output: {}", + lint.name, + lint.path.display(), + e + ); + } } Ok(()) } diff --git a/src/tools/lint-docs/src/main.rs b/src/tools/lint-docs/src/main.rs index 9b75ab45fca..922e70402f2 100644 --- a/src/tools/lint-docs/src/main.rs +++ b/src/tools/lint-docs/src/main.rs @@ -3,7 +3,7 @@ use std::path::PathBuf; fn main() { if let Err(e) = doit() { - println!("error: {}", e); + eprintln!("error: {}", e); std::process::exit(1); } } @@ -15,6 +15,7 @@ fn doit() -> Result<(), Box> { let mut rustc_path = None; let mut rustc_target = None; let mut verbose = false; + let mut validate = false; while let Some(arg) = args.next() { match arg.as_str() { "--src" => { @@ -42,6 +43,7 @@ fn doit() -> Result<(), Box> { }; } "-v" | "--verbose" => verbose = true, + "--validate" => validate = true, s => return Err(format!("unexpected argument `{}`", s).into()), } } @@ -63,6 +65,7 @@ fn doit() -> Result<(), Box> { rustc_path: &rustc_path.unwrap(), rustc_target: &rustc_target.unwrap(), verbose, + validate, }; le.extract_lint_docs() }