Implement shell autocompletion for rule codes

For example:

    $ ruff check --select=EM<Tab>
    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.)
This commit is contained in:
Martin Fischer 2023-02-15 00:33:49 +01:00 committed by Charlie Marsh
parent ca49b00e55
commit 70e378b736
5 changed files with 86 additions and 7 deletions

View file

@ -468,7 +468,7 @@ Options:
--show-settings --show-settings
See the settings Ruff will use to lint a given Python file See the settings Ruff will use to lint a given Python file
-h, --help -h, --help
Print help Print help (see more with '--help')
Rule selection: Rule selection:
--select <RULE_CODE> --select <RULE_CODE>

View file

@ -21,7 +21,7 @@ bisection = { version = "0.1.0" }
bitflags = { version = "1.3.2" } bitflags = { version = "1.3.2" }
cfg-if = { version = "1.0.0" } cfg-if = { version = "1.0.0" }
chrono = { version = "0.4.21", default-features = false, features = ["clock"] } 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" } colored = { version = "2.0.0" }
dirs = { version = "4.0.0" } dirs = { version = "4.0.0" }
fern = { version = "0.6.1" } fern = { version = "0.6.1" }

View file

@ -233,3 +233,76 @@ pub(crate) enum Specificity {
Code4Chars, Code4Chars,
Code5Chars, 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<Self::Value, clap::Error> {
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<Box<dyn Iterator<Item = clap::builder::PossibleValue> + '_>> {
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
}
})),
),
))
}
}
}

View file

@ -109,7 +109,8 @@ pub struct CheckArgs {
long, long,
value_delimiter = ',', value_delimiter = ',',
value_name = "RULE_CODE", value_name = "RULE_CODE",
help_heading = "Rule selection" help_heading = "Rule selection",
hide_possible_values = true
)] )]
pub select: Option<Vec<RuleSelector>>, pub select: Option<Vec<RuleSelector>>,
/// Comma-separated list of rule codes to disable. /// Comma-separated list of rule codes to disable.
@ -117,7 +118,8 @@ pub struct CheckArgs {
long, long,
value_delimiter = ',', value_delimiter = ',',
value_name = "RULE_CODE", value_name = "RULE_CODE",
help_heading = "Rule selection" help_heading = "Rule selection",
hide_possible_values = true
)] )]
pub ignore: Option<Vec<RuleSelector>>, pub ignore: Option<Vec<RuleSelector>>,
/// Like --select, but adds additional rule codes on top of the selected /// Like --select, but adds additional rule codes on top of the selected
@ -126,7 +128,8 @@ pub struct CheckArgs {
long, long,
value_delimiter = ',', value_delimiter = ',',
value_name = "RULE_CODE", value_name = "RULE_CODE",
help_heading = "Rule selection" help_heading = "Rule selection",
hide_possible_values = true
)] )]
pub extend_select: Option<Vec<RuleSelector>>, pub extend_select: Option<Vec<RuleSelector>>,
/// Like --ignore. (Deprecated: You can just use --ignore instead.) /// Like --ignore. (Deprecated: You can just use --ignore instead.)
@ -164,7 +167,8 @@ pub struct CheckArgs {
long, long,
value_delimiter = ',', value_delimiter = ',',
value_name = "RULE_CODE", value_name = "RULE_CODE",
help_heading = "Rule selection" help_heading = "Rule selection",
hide_possible_values = true
)] )]
pub fixable: Option<Vec<RuleSelector>>, pub fixable: Option<Vec<RuleSelector>>,
/// List of rule codes to treat as ineligible for autofix. Only applicable /// List of rule codes to treat as ineligible for autofix. Only applicable
@ -173,7 +177,8 @@ pub struct CheckArgs {
long, long,
value_delimiter = ',', value_delimiter = ',',
value_name = "RULE_CODE", value_name = "RULE_CODE",
help_heading = "Rule selection" help_heading = "Rule selection",
hide_possible_values = true
)] )]
pub unfixable: Option<Vec<RuleSelector>>, pub unfixable: Option<Vec<RuleSelector>>,
/// Respect file exclusions via `.gitignore` and other standard ignore /// Respect file exclusions via `.gitignore` and other standard ignore

View file

@ -57,6 +57,7 @@ pub fn register_rules(input: &Input) -> proc_macro2::TokenStream {
PartialOrd, PartialOrd,
Ord, Ord,
AsRefStr, AsRefStr,
::strum_macros::IntoStaticStr,
)] )]
#[strum(serialize_all = "kebab-case")] #[strum(serialize_all = "kebab-case")]
pub enum Rule { #rule_variants } pub enum Rule { #rule_variants }