mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:22:24 +00:00
Add ruff rule --all
subcommand (with JSON output) (#5059)
## Summary This adds a `ruff rule --all` switch that prints out a human-readable Markdown or a machine-readable JSON document of the lint rules known to Ruff. I needed a machine-readable document of the rules [for a project](https://github.com/astral-sh/ruff/discussions/5078), and figured it could be useful for other people – or tooling! – to be able to interrogate Ruff about its arcane knowledge. The JSON output is an array of the same objects printed by `ruff rule --format=json`. ## Test Plan I ran `ruff rule --all --format=json`. I think more might be needed, but maybe a snapshot test is overkill?
This commit is contained in:
parent
952c623102
commit
d7214e77e6
4 changed files with 106 additions and 57 deletions
|
@ -35,11 +35,17 @@ pub struct Args {
|
|||
pub enum Command {
|
||||
/// Run Ruff on the given files or directories (default).
|
||||
Check(CheckArgs),
|
||||
/// Explain a rule.
|
||||
/// Explain a rule (or all rules).
|
||||
#[clap(alias = "--explain")]
|
||||
#[command(group = clap::ArgGroup::new("selector").multiple(false).required(true))]
|
||||
Rule {
|
||||
#[arg(value_parser=Rule::from_code)]
|
||||
rule: Rule,
|
||||
/// Rule to explain
|
||||
#[arg(value_parser=Rule::from_code, group = "selector")]
|
||||
rule: Option<Rule>,
|
||||
|
||||
/// Explain all rules
|
||||
#[arg(long, conflicts_with = "rule", group = "selector")]
|
||||
all: bool,
|
||||
|
||||
/// Output format
|
||||
#[arg(long, value_enum, default_value = "text")]
|
||||
|
|
|
@ -1,7 +1,9 @@
|
|||
use std::io::{self, BufWriter, Write};
|
||||
|
||||
use anyhow::Result;
|
||||
use serde::Serialize;
|
||||
use serde::ser::SerializeSeq;
|
||||
use serde::{Serialize, Serializer};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
use ruff::registry::{Linter, Rule, RuleNamespace};
|
||||
use ruff_diagnostics::AutofixKind;
|
||||
|
@ -11,72 +13,106 @@ use crate::args::HelpFormat;
|
|||
#[derive(Serialize)]
|
||||
struct Explanation<'a> {
|
||||
name: &'a str,
|
||||
code: &'a str,
|
||||
code: String,
|
||||
linter: &'a str,
|
||||
summary: &'a str,
|
||||
message_formats: &'a [&'a str],
|
||||
autofix: &'a str,
|
||||
autofix: String,
|
||||
explanation: Option<&'a str>,
|
||||
nursery: bool,
|
||||
}
|
||||
|
||||
impl<'a> Explanation<'a> {
|
||||
fn from_rule(rule: &'a Rule) -> Self {
|
||||
let code = rule.noqa_code().to_string();
|
||||
let (linter, _) = Linter::parse_code(&code).unwrap();
|
||||
let autofix = rule.autofixable().to_string();
|
||||
Self {
|
||||
name: rule.as_ref(),
|
||||
code,
|
||||
linter: linter.name(),
|
||||
summary: rule.message_formats()[0],
|
||||
message_formats: rule.message_formats(),
|
||||
autofix,
|
||||
explanation: rule.explanation(),
|
||||
nursery: rule.is_nursery(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn format_rule_text(rule: Rule) -> String {
|
||||
let mut output = String::new();
|
||||
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||
output.push_str(&format!("Derived from the **{}** linter.", linter.name()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let autofix = rule.autofixable();
|
||||
if matches!(autofix, AutofixKind::Always | AutofixKind::Sometimes) {
|
||||
output.push_str(&autofix.to_string());
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_nursery() {
|
||||
output.push_str(&format!(
|
||||
r#"This rule is part of the **nursery**, a collection of newer lints that are
|
||||
still under development. As such, it must be enabled by explicitly selecting
|
||||
{}."#,
|
||||
rule.noqa_code()
|
||||
));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if let Some(explanation) = rule.explanation() {
|
||||
output.push_str(explanation.trim());
|
||||
} else {
|
||||
output.push_str("Message formats:");
|
||||
for format in rule.message_formats() {
|
||||
output.push('\n');
|
||||
output.push_str(&format!("* {format}"));
|
||||
}
|
||||
}
|
||||
output
|
||||
}
|
||||
|
||||
/// Explain a `Rule` to the user.
|
||||
pub(crate) fn rule(rule: Rule, format: HelpFormat) -> Result<()> {
|
||||
let (linter, _) = Linter::parse_code(&rule.noqa_code().to_string()).unwrap();
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
let mut output = String::new();
|
||||
|
||||
match format {
|
||||
HelpFormat::Text => {
|
||||
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
writeln!(stdout, "{}", format_rule_text(rule))?;
|
||||
}
|
||||
HelpFormat::Json => {
|
||||
serde_json::to_writer_pretty(stdout, &Explanation::from_rule(&rule))?;
|
||||
}
|
||||
};
|
||||
Ok(())
|
||||
}
|
||||
|
||||
output.push_str(&format!("Derived from the **{}** linter.", linter.name()));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
|
||||
let autofix = rule.autofixable();
|
||||
if matches!(autofix, AutofixKind::Always | AutofixKind::Sometimes) {
|
||||
output.push_str(&autofix.to_string());
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if rule.is_nursery() {
|
||||
output.push_str(&format!(
|
||||
r#"This rule is part of the **nursery**, a collection of newer lints that are
|
||||
still under development. As such, it must be enabled by explicitly selecting
|
||||
{}."#,
|
||||
rule.noqa_code()
|
||||
));
|
||||
output.push('\n');
|
||||
output.push('\n');
|
||||
}
|
||||
|
||||
if let Some(explanation) = rule.explanation() {
|
||||
output.push_str(explanation.trim());
|
||||
} else {
|
||||
output.push_str("Message formats:");
|
||||
for format in rule.message_formats() {
|
||||
output.push('\n');
|
||||
output.push_str(&format!("* {format}"));
|
||||
}
|
||||
/// Explain all rules to the user.
|
||||
pub(crate) fn rules(format: HelpFormat) -> Result<()> {
|
||||
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||
match format {
|
||||
HelpFormat::Text => {
|
||||
for rule in Rule::iter() {
|
||||
writeln!(stdout, "{}", format_rule_text(rule))?;
|
||||
writeln!(stdout)?;
|
||||
}
|
||||
}
|
||||
HelpFormat::Json => {
|
||||
output.push_str(&serde_json::to_string_pretty(&Explanation {
|
||||
name: rule.as_ref(),
|
||||
code: &rule.noqa_code().to_string(),
|
||||
linter: linter.name(),
|
||||
summary: rule.message_formats()[0],
|
||||
message_formats: rule.message_formats(),
|
||||
autofix: &rule.autofixable().to_string(),
|
||||
explanation: rule.explanation(),
|
||||
})?);
|
||||
let mut serializer = serde_json::Serializer::pretty(stdout);
|
||||
let mut seq = serializer.serialize_seq(None)?;
|
||||
for rule in Rule::iter() {
|
||||
seq.serialize_element(&Explanation::from_rule(&rule))?;
|
||||
}
|
||||
seq.end()?;
|
||||
}
|
||||
};
|
||||
|
||||
writeln!(stdout, "{output}")?;
|
||||
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
@ -134,7 +134,14 @@ quoting the executed command, along with the relevant file contents and `pyproje
|
|||
set_up_logging(&log_level)?;
|
||||
|
||||
match command {
|
||||
Command::Rule { rule, format } => commands::rule::rule(rule, format)?,
|
||||
Command::Rule { rule, all, format } => {
|
||||
if all {
|
||||
commands::rule::rules(format)?;
|
||||
}
|
||||
if let Some(rule) = rule {
|
||||
commands::rule::rule(rule, format)?;
|
||||
}
|
||||
}
|
||||
Command::Config { option } => return Ok(commands::config::config(option.as_deref())),
|
||||
Command::Linter { format } => commands::linter::linter(format)?,
|
||||
Command::Clean => commands::clean::clean(log_level)?,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue