diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 887f935d026cb..b3159f4eb6ab5 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -10,7 +10,7 @@ use ruff_linter::registry::Rule; use ruff_linter::settings::types::{ FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, }; -use ruff_linter::{RuleSelector, RuleSelectorParser}; +use ruff_linter::{RuleParser, RuleSelector, RuleSelectorParser}; use ruff_workspace::configuration::{Configuration, RuleSelection}; use ruff_workspace::resolver::ConfigurationTransformer; @@ -39,7 +39,7 @@ pub enum Command { #[command(group = clap::ArgGroup::new("selector").multiple(false).required(true))] Rule { /// Rule to explain - #[arg(value_parser=Rule::from_code, group = "selector")] + #[arg(value_parser=RuleParser, group = "selector", hide_possible_values = true)] rule: Option, /// Explain all rules diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index e693c64a0f07a..1a4e538bd2ff8 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -225,7 +225,7 @@ fn explain_status_codes_ruf404() { ----- stdout ----- ----- stderr ----- - error: invalid value 'RUF404' for '[RULE]': unknown rule code + error: invalid value 'RUF404' for '[RULE]' For more information, try '--help'. "###); diff --git a/crates/ruff_linter/src/lib.rs b/crates/ruff_linter/src/lib.rs index ae14723abfef1..ee8f238879da8 100644 --- a/crates/ruff_linter/src/lib.rs +++ b/crates/ruff_linter/src/lib.rs @@ -5,6 +5,8 @@ //! //! [Ruff]: https://github.com/astral-sh/ruff +#[cfg(feature = "clap")] +pub use registry::clap_completion::RuleParser; #[cfg(feature = "clap")] pub use rule_selector::clap_completion::RuleSelectorParser; pub use rule_selector::RuleSelector; diff --git a/crates/ruff_linter/src/registry.rs b/crates/ruff_linter/src/registry.rs index 029b98d264837..a7a571e82baf7 100644 --- a/crates/ruff_linter/src/registry.rs +++ b/crates/ruff_linter/src/registry.rs @@ -360,6 +360,64 @@ pub const INCOMPATIBLE_CODES: &[(Rule, Rule, &str); 2] = &[ ), ]; +#[cfg(feature = "clap")] +pub mod clap_completion { + use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory}; + use strum::IntoEnumIterator; + + use crate::registry::Rule; + + #[derive(Clone)] + pub struct RuleParser; + + impl ValueParserFactory for Rule { + type Parser = RuleParser; + + fn value_parser() -> Self::Parser { + RuleParser + } + } + + impl TypedValueParser for RuleParser { + type Value = Rule; + + 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))?; + + Rule::from_code(value).map_err(|_| { + let mut error = + clap::Error::new(clap::error::ErrorKind::ValueValidation).with_cmd(cmd); + if let Some(arg) = arg { + error.insert( + clap::error::ContextKind::InvalidArg, + clap::error::ContextValue::String(arg.to_string()), + ); + } + error.insert( + clap::error::ContextKind::InvalidValue, + clap::error::ContextValue::String(value.to_string()), + ); + error + }) + } + + fn possible_values(&self) -> Option + '_>> { + Some(Box::new(Rule::iter().map(|rule| { + let name = rule.noqa_code().to_string(); + let help = rule.as_ref().to_string(); + PossibleValue::new(name).help(help) + }))) + } + } +} + #[cfg(test)] mod tests { use std::mem::size_of;