diff --git a/README.md b/README.md index d622e901bd..9cd2b35f90 100644 --- a/README.md +++ b/README.md @@ -382,10 +382,11 @@ Ruff: An extremely fast Python linter. Usage: ruff [OPTIONS] Commands: - check Run Ruff on the given files or directories (default) - rule Explain a rule - clean Clear any caches in the current directory and any subdirectories - help Print this message or the help of the given subcommand(s) + check Run Ruff on the given files or directories (default) + rule Explain a rule + linter List all supported upstream linters + clean Clear any caches in the current directory and any subdirectories + help Print this message or the help of the given subcommand(s) Options: -h, --help Print help diff --git a/ruff_cli/Cargo.toml b/ruff_cli/Cargo.toml index 212ddea077..30905d5ecb 100644 --- a/ruff_cli/Cargo.toml +++ b/ruff_cli/Cargo.toml @@ -53,6 +53,7 @@ similar = { version = "2.2.1" } textwrap = { version = "0.16.0" } update-informer = { version = "0.6.0", default-features = false, features = ["pypi"], optional = true } walkdir = { version = "2.3.2" } +strum = "0.24.1" [dev-dependencies] assert_cmd = { version = "2.0.4" } diff --git a/ruff_cli/src/args.rs b/ruff_cli/src/args.rs index 843f7e8c98..c281994753 100644 --- a/ruff_cli/src/args.rs +++ b/ruff_cli/src/args.rs @@ -41,6 +41,12 @@ pub enum Command { #[arg(long, value_enum, default_value = "text")] format: HelpFormat, }, + /// List all supported upstream linters + Linter { + /// Output format + #[arg(long, value_enum, default_value = "text")] + format: HelpFormat, + }, /// Clear any caches in the current directory and any subdirectories. #[clap(alias = "--clean")] Clean, diff --git a/ruff_cli/src/commands.rs b/ruff_cli/src/commands.rs index 7a9a93c9f7..64e8ed4726 100644 --- a/ruff_cli/src/commands.rs +++ b/ruff_cli/src/commands.rs @@ -27,6 +27,8 @@ use crate::cache; use crate::diagnostics::{lint_path, lint_stdin, Diagnostics}; use crate::iterators::par_iter; +pub mod linter; + /// Run the linter over a collection of files. pub fn run( files: &[PathBuf], diff --git a/ruff_cli/src/commands/linter.rs b/ruff_cli/src/commands/linter.rs new file mode 100644 index 0000000000..a083c54126 --- /dev/null +++ b/ruff_cli/src/commands/linter.rs @@ -0,0 +1,59 @@ +use itertools::Itertools; +use serde::Serialize; +use strum::IntoEnumIterator; + +use ruff::registry::{Linter, LinterCategory, RuleNamespace}; + +use crate::args::HelpFormat; + +pub fn linter(format: HelpFormat) { + match format { + HelpFormat::Text => { + for linter in Linter::iter() { + let prefix = match linter.common_prefix() { + "" => linter + .categories() + .unwrap() + .iter() + .map(|LinterCategory(prefix, ..)| prefix) + .join("/"), + prefix => prefix.to_string(), + }; + println!("{:>4} {}", prefix, linter.name()); + } + } + + HelpFormat::Json => { + let linters: Vec<_> = Linter::iter() + .map(|linter_info| LinterInfo { + prefix: linter_info.common_prefix(), + name: linter_info.name(), + categories: linter_info.categories().map(|cats| { + cats.iter() + .map(|LinterCategory(prefix, name, ..)| LinterCategoryInfo { + prefix, + name, + }) + .collect() + }), + }) + .collect(); + + println!("{}", serde_json::to_string_pretty(&linters).unwrap()); + } + } +} + +#[derive(Serialize)] +struct LinterInfo { + prefix: &'static str, + name: &'static str, + #[serde(skip_serializing_if = "Option::is_none")] + categories: Option>, +} + +#[derive(Serialize)] +struct LinterCategoryInfo { + prefix: &'static str, + name: &'static str, +} diff --git a/ruff_cli/src/main.rs b/ruff_cli/src/main.rs index 27f21b549d..1ff7e57721 100644 --- a/ruff_cli/src/main.rs +++ b/ruff_cli/src/main.rs @@ -78,6 +78,7 @@ quoting the executed command, along with the relevant file contents and `pyproje match command { Command::Rule { rule, format } => commands::rule(rule, format)?, + Command::Linter { format } => commands::linter::linter(format), Command::Clean => commands::clean(log_level)?, Command::GenerateShellCompletion { shell } => { shell.generate(&mut Args::command(), &mut io::stdout());