Automatically linkify option references in rule documentation

Previously the rule documentation referenced configuration options
via full https:// URLs, which was bad for several reasons:

* changing the website would mean you'd have to change all URLs
* the links didn't work when building mkdocs locally
* the URLs showed up in the `ruff rule` output
* broken references weren't detected by our CI

This commit solves all of these problems by post-processing the
Markdown, recognizing sections such as:

    ## Options

    * `flake8-tidy-imports.ban-relative-imports`

`cargo dev generate-all` will automatically linkify such references
and panic if the referenced option doesn't exist.
Note that the option can also be linked in the other Markdown sections
via e.g. [`flake8-tidy-imports.ban-relative-imports`] since
the post-processing code generates a CommonMark link definition.

Resolves #2766.
This commit is contained in:
Martin Fischer 2023-02-11 19:57:40 +01:00 committed by Charlie Marsh
parent fc4c927788
commit 28c9263722
13 changed files with 122 additions and 41 deletions

View file

@ -24,10 +24,9 @@ define_violation!(
/// `__init__.py` file is typically meant to be a regular package, and
/// the absence of the `__init__.py` file is probably an oversight.
///
/// Note that namespace packages can be specified via the
/// [`namespace-packages`](https://github.com/charliermarsh/ruff#namespace-packages)
/// configuration option. Adding a namespace package to the configuration
/// will suppress this violation for a given package.
/// ## Options
///
/// * `namespace-packages`
pub struct ImplicitNamespacePackage {
pub filename: String,
}

View file

@ -14,13 +14,16 @@ use crate::violation::AlwaysAutofixableViolation;
define_violation!(
/// ## What it does
/// Checks for inline strings that use single quotes or double quotes,
/// depending on the value of the [`inline-quotes`](https://github.com/charliermarsh/ruff#inline-quotes)
/// setting.
/// depending on the value of the [`flake8-quotes.inline-quotes`] option.
///
/// ## Why is this bad?
/// Consistency is good. Use either single or double quotes for inline
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.inline-quotes`
///
/// ## Example
/// ```python
/// foo = 'bar'
@ -56,13 +59,17 @@ impl AlwaysAutofixableViolation for BadQuotesInlineString {
define_violation!(
/// ## What it does
/// Checks for multiline strings that use single quotes or double quotes,
/// depending on the value of the [`multiline-quotes`](https://github.com/charliermarsh/ruff#multiline-quotes)
/// depending on the value of the [`flake8-quotes.multiline-quotes`]
/// setting.
///
/// ## Why is this bad?
/// Consistency is good. Use either single or double quotes for multiline
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.multiline-quotes`
///
/// ## Example
/// ```python
/// foo = '''
@ -101,13 +108,17 @@ impl AlwaysAutofixableViolation for BadQuotesMultilineString {
define_violation!(
/// ## What it does
/// Checks for docstrings that use single quotes or double quotes, depending on the value of the [`docstring-quotes`](https://github.com/charliermarsh/ruff#docstring-quotes)
/// setting.
/// Checks for docstrings that use single quotes or double quotes, depending
/// on the value of the [`flake8-quotes.docstring-quotes`] setting.
///
/// ## Why is this bad?
/// Consistency is good. Use either single or double quotes for docstring
/// strings, but be consistent.
///
/// ## Options
///
/// * `flake8-quotes.docstring-quotes`
///
/// ## Example
/// ```python
/// '''

View file

@ -48,11 +48,9 @@ define_violation!(
/// > from .sibling import example
/// > ```
///
/// Note that degree of strictness packages can be specified via the
/// [`ban-relative-imports`](https://github.com/charliermarsh/ruff#ban-relative-imports)
/// configuration option, which allows banning all relative imports
/// (`ban-relative-imports = "all"`) or only those that extend into the parent module or beyond
/// (`ban-relative-imports = "parents"`, the default).
/// ## Options
///
/// * `flake8-tidy-imports.ban-relative-imports`
///
/// ## Example
/// ```python

View file

@ -18,6 +18,10 @@ define_violation!(
/// ## Why is this bad?
/// Functions with a high complexity are hard to understand and maintain.
///
/// ## Options
///
/// * `mccabe.max-complexity`
///
/// ## Example
/// ```python
/// def foo(a, b, c):

View file

@ -23,9 +23,13 @@ define_violation!(
/// A variable that is defined but not used is likely a mistake, and should be
/// removed to avoid confusion.
///
/// If a variable is intentionally defined-but-not-used, it should be prefixed
/// with an underscore, or some other value that adheres to the
/// [`dummy-variable-rgx`](https://github.com/charliermarsh/ruff#dummy-variable-rgx) pattern.
/// If a variable is intentionally defined-but-not-used, it should be
/// prefixed with an underscore, or some other value that adheres to the
/// [`dummy-variable-rgx`] pattern.
///
/// ## Options
///
/// * `dummy-variable-rgx`
///
/// ## Example
/// ```python

View file

@ -5,6 +5,8 @@ use std::fs;
use anyhow::Result;
use ruff::registry::{Linter, Rule, RuleNamespace};
use ruff::settings::options::Options;
use ruff::settings::options_base::ConfigurationOptions;
use ruff::AutofixAvailability;
use strum::IntoEnumIterator;
@ -37,7 +39,7 @@ pub fn main(args: &Args) -> Result<()> {
output.push('\n');
}
output.push_str(explanation.trim());
process_documentation(explanation.trim(), &mut output);
if args.dry_run {
println!("{output}");
@ -49,3 +51,35 @@ pub fn main(args: &Args) -> Result<()> {
}
Ok(())
}
fn process_documentation(documentation: &str, out: &mut String) {
let mut in_options = false;
let mut after = String::new();
for line in documentation.split_inclusive('\n') {
if line.starts_with("## ") {
in_options = line == "## Options\n";
} else if in_options {
if let Some(rest) = line.strip_prefix("* `") {
let option = rest.trim_end().trim_end_matches('`');
assert!(
Options::get(Some(option)).is_some(),
"unknown option {option}"
);
let anchor = option.rsplit('.').next().unwrap();
out.push_str(&format!("* [`{option}`]\n"));
after.push_str(&format!("[`{option}`]: ../../settings#{anchor}"));
continue;
}
}
out.push_str(line);
}
if !after.is_empty() {
out.push_str("\n\n");
out.push_str(&after);
}
}

View file

@ -5,13 +5,17 @@ Derived from the **flake8-quotes** linter.
Autofix is always available.
## What it does
Checks for docstrings that use single quotes or double quotes, depending on the value of the [`docstring-quotes`](https://github.com/charliermarsh/ruff#docstring-quotes)
setting.
Checks for docstrings that use single quotes or double quotes, depending
on the value of the [`flake8-quotes.docstring-quotes`] setting.
## Why is this bad?
Consistency is good. Use either single or double quotes for docstring
strings, but be consistent.
## Options
* [`flake8-quotes.docstring-quotes`]
## Example
```python
'''
@ -24,4 +28,6 @@ Assuming `docstring-quotes` is set to `double`, use instead:
"""
bar
"""
```
```
[`flake8-quotes.docstring-quotes`]: ../../settings#docstring-quotes

View file

@ -6,13 +6,16 @@ Autofix is always available.
## What it does
Checks for inline strings that use single quotes or double quotes,
depending on the value of the [`inline-quotes`](https://github.com/charliermarsh/ruff#inline-quotes)
setting.
depending on the value of the [`flake8-quotes.inline-quotes`] option.
## Why is this bad?
Consistency is good. Use either single or double quotes for inline
strings, but be consistent.
## Options
* [`flake8-quotes.inline-quotes`]
## Example
```python
foo = 'bar'
@ -21,4 +24,6 @@ foo = 'bar'
Assuming `inline-quotes` is set to `double`, use instead:
```python
foo = "bar"
```
```
[`flake8-quotes.inline-quotes`]: ../../settings#inline-quotes

View file

@ -6,13 +6,17 @@ Autofix is always available.
## What it does
Checks for multiline strings that use single quotes or double quotes,
depending on the value of the [`multiline-quotes`](https://github.com/charliermarsh/ruff#multiline-quotes)
depending on the value of the [`flake8-quotes.multiline-quotes`]
setting.
## Why is this bad?
Consistency is good. Use either single or double quotes for multiline
strings, but be consistent.
## Options
* [`flake8-quotes.multiline-quotes`]
## Example
```python
foo = '''
@ -25,4 +29,6 @@ Assuming `multiline-quotes` is set to `double`, use instead:
foo = """
bar
"""
```
```
[`flake8-quotes.multiline-quotes`]: ../../settings#multiline-quotes

View file

@ -13,6 +13,10 @@ the code where the program has a choice of two or more paths to follow.
## Why is this bad?
Functions with a high complexity are hard to understand and maintain.
## Options
* [`mccabe.max-complexity`]
## Example
```python
def foo(a, b, c):
@ -38,4 +42,6 @@ def foo(a, b, c):
if not c:
return 2
return 1
```
```
[`mccabe.max-complexity`]: ../../settings#max-complexity

View file

@ -18,7 +18,9 @@ Namespace packages are less widely used, so a package that lacks an
`__init__.py` file is typically meant to be a regular package, and
the absence of the `__init__.py` file is probably an oversight.
Note that namespace packages can be specified via the
[`namespace-packages`](https://github.com/charliermarsh/ruff#namespace-packages)
configuration option. Adding a namespace package to the configuration
will suppress this violation for a given package.
## Options
* [`namespace-packages`]
[`namespace-packages`]: ../../settings#namespace-packages

View file

@ -24,11 +24,9 @@ Absolute imports, or relative imports from siblings, are recommended by [PEP 8](
> from .sibling import example
> ```
Note that degree of strictness packages can be specified via the
[`ban-relative-imports`](https://github.com/charliermarsh/ruff#ban-relative-imports)
configuration option, which allows banning all relative imports
(`ban-relative-imports = "all"`) or only those that extend into the parent module or beyond
(`ban-relative-imports = "parents"`, the default).
## Options
* [`flake8-tidy-imports.ban-relative-imports`]
## Example
```python
@ -38,4 +36,6 @@ from .. import foo
Use instead:
```python
from mypkg import foo
```
```
[`flake8-tidy-imports.ban-relative-imports`]: ../../settings#ban-relative-imports

View file

@ -11,9 +11,13 @@ Checks for the presence of unused variables in function scopes.
A variable that is defined but not used is likely a mistake, and should be
removed to avoid confusion.
If a variable is intentionally defined-but-not-used, it should be prefixed
with an underscore, or some other value that adheres to the
[`dummy-variable-rgx`](https://github.com/charliermarsh/ruff#dummy-variable-rgx) pattern.
If a variable is intentionally defined-but-not-used, it should be
prefixed with an underscore, or some other value that adheres to the
[`dummy-variable-rgx`] pattern.
## Options
* [`dummy-variable-rgx`]
## Example
```python
@ -28,4 +32,6 @@ Use instead:
def foo():
x = 1
return x
```
```
[`dummy-variable-rgx`]: ../../settings#dummy-variable-rgx