diff options
Diffstat (limited to '')
-rw-r--r-- | src/tools/lint-docs/src/groups.rs | 148 |
1 files changed, 148 insertions, 0 deletions
diff --git a/src/tools/lint-docs/src/groups.rs b/src/tools/lint-docs/src/groups.rs new file mode 100644 index 000000000..9696e35b7 --- /dev/null +++ b/src/tools/lint-docs/src/groups.rs @@ -0,0 +1,148 @@ +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"), + ("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"), +]; + +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: {:?}\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) + } +} |