blob: c5cd30ccc34375cd0f17ca9c2e33394d8d201b77 [file] [log] [blame]
use crate::{Lint, LintExtractor};
use std::collections::{BTreeMap, BTreeSet};
use std::error::Error;
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"),
("let-underscore", "Lints that detect wildcard let bindings that are likely to be invalid"),
("rustdoc", "Rustdoc-specific lints"),
("rust-2018-idioms", "Lints to nudge you toward idiomatic features of Rust 2018"),
("nonstandard-style", "Violation of standard naming conventions"),
("future-incompatible", "Lints that detect code that has future-compatibility problems"),
("rust-2018-compatibility", "Lints used to transition code from the 2015 edition to 2018"),
("rust-2021-compatibility", "Lints used to transition code from the 2018 edition to 2021"),
("rust-2024-compatibility", "Lints used to transition code from the 2021 edition to 2024"),
];
type LintGroups = BTreeMap<String, BTreeSet<String>>;
impl<'a> LintExtractor<'a> {
/// Updates the documentation of lint groups.
pub(crate) fn generate_group_docs(&self, lints: &[Lint]) -> Result<(), Box<dyn Error>> {
let groups = self.collect_groups()?;
let groups_path = self.out_path.join("groups.md");
let contents = fs::read_to_string(&groups_path)
.map_err(|e| format!("could not read {}: {}", groups_path.display(), e))?;
let new_contents =
contents.replace("{{groups-table}}", &self.make_groups_table(lints, &groups)?);
// Delete the output because rustbuild uses hard links in its copies.
let _ = fs::remove_file(&groups_path);
fs::write(&groups_path, new_contents)
.map_err(|e| format!("could not write to {}: {}", groups_path.display(), e))?;
Ok(())
}
/// Collects the group names from rustc.
fn collect_groups(&self) -> Result<LintGroups, Box<dyn Error>> {
let mut result = BTreeMap::new();
let mut cmd = Command::new(self.rustc_path);
cmd.arg("-Whelp");
let output = cmd.output().map_err(|e| format!("failed to run command {:?}\n{}", cmd, e))?;
if !output.status.success() {
return Err(format!(
"failed to collect lint info: failed to run {cmd:?}: {:?}\n--- stderr\n{}--- stdout\n{}\n",
output.status,
std::str::from_utf8(&output.stderr).unwrap(),
std::str::from_utf8(&output.stdout).unwrap(),
)
.into());
}
let stdout = std::str::from_utf8(&output.stdout).unwrap();
let lines = stdout.lines();
let group_start = lines.skip_while(|line| !line.contains("groups provided")).skip(1);
let table_start = group_start.skip_while(|line| !line.contains("----")).skip(1);
for line in table_start {
if line.is_empty() {
break;
}
let mut parts = line.trim().splitn(2, ' ');
let name = parts.next().expect("name in group");
if name == "warnings" {
// This is special.
continue;
}
let lints = parts
.next()
.ok_or_else(|| format!("expected lints following name, got `{}`", line))?;
let lints = lints.split(',').map(|l| l.trim().to_string()).collect();
assert!(result.insert(name.to_string(), lints).is_none());
}
if result.is_empty() {
return Err(
format!("expected at least one group in -Whelp output, got:\n{}", stdout).into()
);
}
Ok(result)
}
fn make_groups_table(
&self,
lints: &[Lint],
groups: &LintGroups,
) -> Result<String, Box<dyn Error>> {
let mut result = String::new();
let mut to_link = Vec::new();
result.push_str("| Group | Description | Lints |\n");
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 = 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 in \
src/tools/lint-docs/src/groups.rs",
group_name
)
.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(", "))
.unwrap();
}
result.push('\n');
result.push_str("[warn-by-default]: listing/warn-by-default.md\n");
for lint_name in to_link {
let lint_def = match lints.iter().find(|l| l.name == lint_name.replace("-", "_")) {
Some(def) => def,
None => {
let msg = format!(
"`rustc -W help` defined lint `{}` but that lint does not \
appear to exist\n\
Check that the lint definition includes the appropriate doc comments.",
lint_name
);
if self.validate {
return Err(msg.into());
} else {
eprintln!("warning: {}", msg);
continue;
}
}
};
write!(
result,
"[{}]: listing/{}#{}\n",
lint_name,
lint_def.level.doc_filename(),
lint_name
)
.unwrap();
}
Ok(result)
}
}