From 08c769207852201607a1cfe752390d8e0cff9184 Mon Sep 17 00:00:00 2001 From: Martin Fischer Date: Wed, 15 Feb 2023 00:33:49 +0100 Subject: [PATCH] Implement shell autocompletion for rule codes For example: $ ruff check --select=EM EM -- flake8-errmsg EM10 EM1 -- EM101 -- raw-string-in-exception EM102 -- f-string-in-exception EM103 -- dot-format-in-exception (You will need to enable autocompletion as described in the Autocompletion section in the README.) Fixes #2808. (The --help help change in the README is due to a clap bug, for which I already submitted a fix: https://github.com/clap-rs/clap/pull/4710.) --- README.md | 2 +- crates/ruff/Cargo.toml | 2 +- crates/ruff/src/rule_selector.rs | 73 ++++++++++++++++++++++++ crates/ruff_cli/src/args.rs | 15 +++-- crates/ruff_macros/src/register_rules.rs | 1 + 5 files changed, 86 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 1c77d5fbe1fbc..b00ee02f9ba75 100644 --- a/README.md +++ b/README.md @@ -468,7 +468,7 @@ Options: --show-settings See the settings Ruff will use to lint a given Python file -h, --help - Print help + Print help (see more with '--help') Rule selection: --select diff --git a/crates/ruff/Cargo.toml b/crates/ruff/Cargo.toml index 6d843c6302d5d..5327383adad9e 100644 --- a/crates/ruff/Cargo.toml +++ b/crates/ruff/Cargo.toml @@ -21,7 +21,7 @@ bisection = { version = "0.1.0" } bitflags = { version = "1.3.2" } cfg-if = { version = "1.0.0" } chrono = { version = "0.4.21", default-features = false, features = ["clock"] } -clap = { workspace = true, features = ["derive", "env"] } +clap = { workspace = true, features = ["derive", "env", "string"] } colored = { version = "2.0.0" } dirs = { version = "4.0.0" } fern = { version = "0.6.1" } diff --git a/crates/ruff/src/rule_selector.rs b/crates/ruff/src/rule_selector.rs index 20996ebc13684..9b21d23c747f2 100644 --- a/crates/ruff/src/rule_selector.rs +++ b/crates/ruff/src/rule_selector.rs @@ -233,3 +233,76 @@ pub(crate) enum Specificity { Code4Chars, Code5Chars, } + +mod clap_completion { + use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory}; + use strum::IntoEnumIterator; + + use crate::{ + codes::RuleCodePrefix, + registry::{Linter, RuleNamespace}, + RuleSelector, + }; + + #[derive(Clone)] + pub struct RuleSelectorParser; + + impl ValueParserFactory for RuleSelector { + type Parser = RuleSelectorParser; + + fn value_parser() -> Self::Parser { + RuleSelectorParser + } + } + + impl TypedValueParser for RuleSelectorParser { + type Value = RuleSelector; + + fn parse_ref( + &self, + _cmd: &clap::Command, + _arg: Option<&clap::Arg>, + value: &std::ffi::OsStr, + ) -> Result { + let value = value + .to_str() + .ok_or_else(|| clap::Error::new(clap::error::ErrorKind::InvalidUtf8))?; + + value + .parse() + .map_err(|e| clap::Error::raw(clap::error::ErrorKind::InvalidValue, e)) + } + + fn possible_values( + &self, + ) -> Option + '_>> { + Some(Box::new( + std::iter::once(PossibleValue::new("ALL").help("all rules")).chain( + Linter::iter() + .filter_map(|l| { + let prefix = l.common_prefix(); + (!prefix.is_empty()).then(|| PossibleValue::new(prefix).help(l.name())) + }) + .chain(RuleCodePrefix::iter().map(|p| { + let prefix = p.linter().common_prefix(); + let code = p.short_code(); + + let mut rules_iter = p.into_iter(); + let rule1 = rules_iter.next(); + let rule2 = rules_iter.next(); + + let value = PossibleValue::new(format!("{prefix}{code}")); + + if rule2.is_none() { + let rule1 = rule1.unwrap(); + let name: &'static str = rule1.into(); + value.help(name) + } else { + value + } + })), + ), + )) + } + } +} diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 5980c7e6c65a3..445534c759974 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -109,7 +109,8 @@ pub struct CheckArgs { long, value_delimiter = ',', value_name = "RULE_CODE", - help_heading = "Rule selection" + help_heading = "Rule selection", + hide_possible_values = true )] pub select: Option>, /// Comma-separated list of rule codes to disable. @@ -117,7 +118,8 @@ pub struct CheckArgs { long, value_delimiter = ',', value_name = "RULE_CODE", - help_heading = "Rule selection" + help_heading = "Rule selection", + hide_possible_values = true )] pub ignore: Option>, /// Like --select, but adds additional rule codes on top of the selected @@ -126,7 +128,8 @@ pub struct CheckArgs { long, value_delimiter = ',', value_name = "RULE_CODE", - help_heading = "Rule selection" + help_heading = "Rule selection", + hide_possible_values = true )] pub extend_select: Option>, /// Like --ignore. (Deprecated: You can just use --ignore instead.) @@ -164,7 +167,8 @@ pub struct CheckArgs { long, value_delimiter = ',', value_name = "RULE_CODE", - help_heading = "Rule selection" + help_heading = "Rule selection", + hide_possible_values = true )] pub fixable: Option>, /// List of rule codes to treat as ineligible for autofix. Only applicable @@ -173,7 +177,8 @@ pub struct CheckArgs { long, value_delimiter = ',', value_name = "RULE_CODE", - help_heading = "Rule selection" + help_heading = "Rule selection", + hide_possible_values = true )] pub unfixable: Option>, /// Respect file exclusions via `.gitignore` and other standard ignore diff --git a/crates/ruff_macros/src/register_rules.rs b/crates/ruff_macros/src/register_rules.rs index 7d48ec12b2853..08a0372f2883a 100644 --- a/crates/ruff_macros/src/register_rules.rs +++ b/crates/ruff_macros/src/register_rules.rs @@ -57,6 +57,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream { PartialOrd, Ord, AsRefStr, + ::strum_macros::IntoStaticStr, )] #[strum(serialize_all = "kebab-case")] pub enum Rule { #rule_variants }