lint-docs: Add --validate flag to validate lint docs separately.

This commit is contained in:
Eric Huss 2020-11-28 13:29:51 -08:00
parent f17e6487b2
commit d2d91b42a5
7 changed files with 115 additions and 23 deletions

View File

@ -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) => (

View File

@ -413,6 +413,7 @@ impl<'a> Builder<'a> {
test::TheBook,
test::UnstableBook,
test::RustcBook,
test::LintDocs,
test::RustcGuide,
test::EmbeddedBook,
test::EditionGuide,

View File

@ -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.

View File

@ -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,
});
}
}

View File

@ -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(", "))

View File

@ -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() {
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(());
}
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(())
}

View File

@ -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<dyn Error>> {
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<dyn Error>> {
};
}
"-v" | "--verbose" => verbose = true,
"--validate" => validate = true,
s => return Err(format!("unexpected argument `{}`", s).into()),
}
}
@ -63,6 +65,7 @@ fn doit() -> Result<(), Box<dyn Error>> {
rustc_path: &rustc_path.unwrap(),
rustc_target: &rustc_target.unwrap(),
verbose,
validate,
};
le.extract_lint_docs()
}