Docs: Link inline settings when not part of options section (#10499)

## Summary
 
Some contributors have referenced settings in their documentation
without adding the settings to an options section, this has lead to some
rendering issues (#10427). This PR addresses this looking for potential
inline links to settings, cross-checking them with the options sections,
and then linking them anyway if they are not found.

Resolves #10427.

## Test Plan

Manually verified that the correct modifications were made and no docs
were broken.
This commit is contained in:
Auguste Lalande 2024-03-21 12:33:58 -04:00 committed by GitHub
parent caa1450895
commit 9b3c732538
No known key found for this signature in database
GPG key ID: B5690EEEBB952194

View file

@ -1,6 +1,7 @@
//! Generate Markdown documentation for applicable rules. //! Generate Markdown documentation for applicable rules.
#![allow(clippy::print_stdout, clippy::print_stderr)] #![allow(clippy::print_stdout, clippy::print_stderr)]
use std::collections::HashSet;
use std::fs; use std::fs;
use std::path::PathBuf; use std::path::PathBuf;
@ -97,20 +98,22 @@ pub(crate) fn main(args: &Args) -> Result<()> {
fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) { fn process_documentation(documentation: &str, out: &mut String, rule_name: &str) {
let mut in_options = false; let mut in_options = false;
let mut after = String::new(); let mut after = String::new();
let mut referenced_options = HashSet::new();
// HACK: This is an ugly regex hack that's necessary because mkdocs uses // HACK: This is an ugly regex hack that's necessary because mkdocs uses
// a non-CommonMark-compliant Markdown parser, which doesn't support code // a non-CommonMark-compliant Markdown parser, which doesn't support code
// tags in link definitions // tags in link definitions
// (see https://github.com/Python-Markdown/markdown/issues/280). // (see https://github.com/Python-Markdown/markdown/issues/280).
let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[\(])") let documentation = Regex::new(r"\[`([^`]*?)`]($|[^\[(])").unwrap().replace_all(
.unwrap() documentation,
.replace_all(documentation, |caps: &Captures| { |caps: &Captures| {
format!( format!(
"[`{option}`][{option}]{sep}", "[`{option}`][{option}]{sep}",
option = &caps[1], option = &caps[1],
sep = &caps[2] sep = &caps[2]
) )
}); },
);
for line in documentation.split_inclusive('\n') { for line in documentation.split_inclusive('\n') {
if line.starts_with("## ") { if line.starts_with("## ") {
@ -134,6 +137,7 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
let anchor = option.replace('.', "_"); let anchor = option.replace('.', "_");
out.push_str(&format!("- [`{option}`][{option}]\n")); out.push_str(&format!("- [`{option}`][{option}]\n"));
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n")); after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
referenced_options.insert(option);
continue; continue;
} }
@ -141,6 +145,20 @@ fn process_documentation(documentation: &str, out: &mut String, rule_name: &str)
out.push_str(line); out.push_str(line);
} }
let re = Regex::new(r"\[`([^`]*?)`]\[(.*?)]").unwrap();
for (_, [option, _]) in re.captures_iter(&documentation).map(|c| c.extract()) {
if let Some(OptionEntry::Field(field)) = Options::metadata().find(option) {
if referenced_options.insert(option) {
let anchor = option.replace('.', "_");
after.push_str(&format!("[{option}]: ../settings.md#{anchor}\n"));
}
if field.deprecated.is_some() {
eprintln!("Rule {rule_name} references deprecated option {option}.");
}
}
}
if !after.is_empty() { if !after.is_empty() {
out.push('\n'); out.push('\n');
out.push('\n'); out.push('\n');