blob: 197dce893a627df1336f59cbc614b4e6d6976c3c [file] [log] [blame]
use cargo::lints::Lint;
use cargo::lints::LintLevel;
use cargo::util::command_prelude::{ArgMatchesExt, flag};
use itertools::Itertools;
use std::fmt::Write;
use std::path::PathBuf;
fn cli() -> clap::Command {
clap::Command::new("xtask-lint-docs").arg(flag("check", "Check that the docs are up-to-date"))
}
fn main() -> anyhow::Result<()> {
let args = cli().get_matches();
let check = args.flag("check");
let mut allow = Vec::new();
let mut warn = Vec::new();
let mut deny = Vec::new();
let mut forbid = Vec::new();
let mut lint_docs = String::new();
for lint in cargo::lints::LINTS.iter().sorted_by_key(|lint| lint.name) {
if lint.docs.is_some() {
let sectipn = match lint.primary_group.default_level {
LintLevel::Allow => &mut allow,
LintLevel::Warn => &mut warn,
LintLevel::Deny => &mut deny,
LintLevel::Forbid => &mut forbid,
};
sectipn.push(lint.name);
add_lint(lint, &mut lint_docs)?;
}
}
let mut buf = String::new();
writeln!(buf, "# Lints\n")?;
writeln!(
buf,
"Note: [Cargo's linting system is unstable](unstable.md#lintscargo) and can only be used on nightly toolchains"
)?;
writeln!(buf)?;
lint_groups(&mut buf)?;
if !allow.is_empty() {
add_level_section(LintLevel::Allow, &allow, &mut buf)?;
}
if !warn.is_empty() {
add_level_section(LintLevel::Warn, &warn, &mut buf)?;
}
if !deny.is_empty() {
add_level_section(LintLevel::Deny, &deny, &mut buf)?;
}
if !forbid.is_empty() {
add_level_section(LintLevel::Forbid, &forbid, &mut buf)?;
}
buf.push_str(&lint_docs);
if check {
let old = std::fs::read_to_string(lint_docs_path())?;
if old != buf {
anyhow::bail!(
"The lints documentation is out-of-date. Run `cargo lint-docs` to update it."
);
}
} else {
std::fs::write(lint_docs_path(), buf)?;
}
Ok(())
}
fn lint_groups(buf: &mut String) -> anyhow::Result<()> {
let (max_name_len, max_desc_len) = cargo::lints::LINT_GROUPS.iter().filter(|g| !g.hidden).fold(
(0, 0),
|(max_name_len, max_desc_len), group| {
// We add 9 to account for the "cargo::" prefix and backticks
let name_len = group.name.chars().count() + 9;
let desc_len = group.desc.chars().count();
(max_name_len.max(name_len), max_desc_len.max(desc_len))
},
);
let default_level_len = "Default level".chars().count();
writeln!(buf, "\n")?;
writeln!(
buf,
"| {:<max_name_len$} | {:<max_desc_len$} | Default level |",
"Group", "Description",
)?;
writeln!(
buf,
"|-{}-|-{}-|-{}-|",
"-".repeat(max_name_len),
"-".repeat(max_desc_len),
"-".repeat(default_level_len)
)?;
for group in cargo::lints::LINT_GROUPS.iter() {
if group.hidden {
continue;
}
let group_name = format!("`cargo::{}`", group.name);
writeln!(
buf,
"| {:<max_name_len$} | {:<max_desc_len$} | {:<default_level_len$} |",
group_name,
group.desc,
group.default_level.to_string(),
)?;
}
writeln!(buf, "\n")?;
Ok(())
}
fn add_lint(lint: &Lint, buf: &mut String) -> std::fmt::Result {
writeln!(buf, "## `{}`", lint.name)?;
writeln!(buf, "Group: `{}`\n", lint.primary_group.name)?;
writeln!(buf, "Level: `{}`", lint.primary_group.default_level)?;
writeln!(buf, "{}\n", lint.docs.as_ref().unwrap())
}
fn add_level_section(level: LintLevel, lint_names: &[&str], buf: &mut String) -> std::fmt::Result {
let title = match level {
LintLevel::Allow => "Allowed-by-default",
LintLevel::Warn => "Warn-by-default",
LintLevel::Deny => "Deny-by-default",
LintLevel::Forbid => "Forbid-by-default",
};
writeln!(buf, "## {title}\n")?;
writeln!(
buf,
"These lints are all set to the '{}' level by default.",
level
)?;
for name in lint_names {
writeln!(buf, "- [`{}`](#{})", name, name)?;
}
writeln!(buf)?;
Ok(())
}
fn lint_docs_path() -> PathBuf {
let pkg_root = env!("CARGO_MANIFEST_DIR");
let ws_root = PathBuf::from(format!("{pkg_root}/../.."));
let path = {
let path = ws_root.join("src/doc/src/reference/lints.md");
path.canonicalize().unwrap_or(path)
};
path
}