mirror of
https://github.com/astral-sh/ruff.git
synced 2025-09-26 11:59:10 +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 {
|
pub enum Command {
|
||||||
/// Run Ruff on the given files or directories (default).
|
/// Run Ruff on the given files or directories (default).
|
||||||
Check(CheckArgs),
|
Check(CheckArgs),
|
||||||
/// Explain a rule.
|
/// Explain a rule (or all rules).
|
||||||
#[clap(alias = "--explain")]
|
#[clap(alias = "--explain")]
|
||||||
|
#[command(group = clap::ArgGroup::new("selector").multiple(false).required(true))]
|
||||||
Rule {
|
Rule {
|
||||||
#[arg(value_parser=Rule::from_code)]
|
/// Rule to explain
|
||||||
rule: Rule,
|
#[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
|
/// Output format
|
||||||
#[arg(long, value_enum, default_value = "text")]
|
#[arg(long, value_enum, default_value = "text")]
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
use std::io::{self, BufWriter, Write};
|
use std::io::{self, BufWriter, Write};
|
||||||
|
|
||||||
use anyhow::Result;
|
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::registry::{Linter, Rule, RuleNamespace};
|
||||||
use ruff_diagnostics::AutofixKind;
|
use ruff_diagnostics::AutofixKind;
|
||||||
|
@ -11,72 +13,106 @@ use crate::args::HelpFormat;
|
||||||
#[derive(Serialize)]
|
#[derive(Serialize)]
|
||||||
struct Explanation<'a> {
|
struct Explanation<'a> {
|
||||||
name: &'a str,
|
name: &'a str,
|
||||||
code: &'a str,
|
code: String,
|
||||||
linter: &'a str,
|
linter: &'a str,
|
||||||
summary: &'a str,
|
summary: &'a str,
|
||||||
message_formats: &'a [&'a str],
|
message_formats: &'a [&'a str],
|
||||||
autofix: &'a str,
|
autofix: String,
|
||||||
explanation: Option<&'a str>,
|
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.
|
/// Explain a `Rule` to the user.
|
||||||
pub(crate) fn rule(rule: Rule, format: HelpFormat) -> Result<()> {
|
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 stdout = BufWriter::new(io::stdout().lock());
|
||||||
let mut output = String::new();
|
|
||||||
|
|
||||||
match format {
|
match format {
|
||||||
HelpFormat::Text => {
|
HelpFormat::Text => {
|
||||||
output.push_str(&format!("# {} ({})", rule.as_ref(), rule.noqa_code()));
|
writeln!(stdout, "{}", format_rule_text(rule))?;
|
||||||
output.push('\n');
|
}
|
||||||
output.push('\n');
|
HelpFormat::Json => {
|
||||||
|
serde_json::to_writer_pretty(stdout, &Explanation::from_rule(&rule))?;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
output.push_str(&format!("Derived from the **{}** linter.", linter.name()));
|
/// Explain all rules to the user.
|
||||||
output.push('\n');
|
pub(crate) fn rules(format: HelpFormat) -> Result<()> {
|
||||||
output.push('\n');
|
let mut stdout = BufWriter::new(io::stdout().lock());
|
||||||
|
match format {
|
||||||
let autofix = rule.autofixable();
|
HelpFormat::Text => {
|
||||||
if matches!(autofix, AutofixKind::Always | AutofixKind::Sometimes) {
|
for rule in Rule::iter() {
|
||||||
output.push_str(&autofix.to_string());
|
writeln!(stdout, "{}", format_rule_text(rule))?;
|
||||||
output.push('\n');
|
writeln!(stdout)?;
|
||||||
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}"));
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HelpFormat::Json => {
|
HelpFormat::Json => {
|
||||||
output.push_str(&serde_json::to_string_pretty(&Explanation {
|
let mut serializer = serde_json::Serializer::pretty(stdout);
|
||||||
name: rule.as_ref(),
|
let mut seq = serializer.serialize_seq(None)?;
|
||||||
code: &rule.noqa_code().to_string(),
|
for rule in Rule::iter() {
|
||||||
linter: linter.name(),
|
seq.serialize_element(&Explanation::from_rule(&rule))?;
|
||||||
summary: rule.message_formats()[0],
|
}
|
||||||
message_formats: rule.message_formats(),
|
seq.end()?;
|
||||||
autofix: &rule.autofixable().to_string(),
|
|
||||||
explanation: rule.explanation(),
|
|
||||||
})?);
|
|
||||||
}
|
}
|
||||||
};
|
}
|
||||||
|
|
||||||
writeln!(stdout, "{output}")?;
|
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -134,7 +134,14 @@ quoting the executed command, along with the relevant file contents and `pyproje
|
||||||
set_up_logging(&log_level)?;
|
set_up_logging(&log_level)?;
|
||||||
|
|
||||||
match command {
|
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::Config { option } => return Ok(commands::config::config(option.as_deref())),
|
||||||
Command::Linter { format } => commands::linter::linter(format)?,
|
Command::Linter { format } => commands::linter::linter(format)?,
|
||||||
Command::Clean => commands::clean::clean(log_level)?,
|
Command::Clean => commands::clean::clean(log_level)?,
|
||||||
|
|
|
@ -161,7 +161,7 @@ Usage: ruff [OPTIONS] <COMMAND>
|
||||||
|
|
||||||
Commands:
|
Commands:
|
||||||
check Run Ruff on the given files or directories (default)
|
check Run Ruff on the given files or directories (default)
|
||||||
rule Explain a rule
|
rule Explain a rule (or all rules)
|
||||||
config List or describe the available configuration options
|
config List or describe the available configuration options
|
||||||
linter List all supported upstream linters
|
linter List all supported upstream linters
|
||||||
clean Clear any caches in the current directory and any subdirectories
|
clean Clear any caches in the current directory and any subdirectories
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue