diff --git a/crates/ruff/src/lib.rs b/crates/ruff/src/lib.rs index 676e521dfeca9..ae14723abfef1 100644 --- a/crates/ruff/src/lib.rs +++ b/crates/ruff/src/lib.rs @@ -5,6 +5,8 @@ //! //! [Ruff]: https://github.com/astral-sh/ruff +#[cfg(feature = "clap")] +pub use rule_selector::clap_completion::RuleSelectorParser; pub use rule_selector::RuleSelector; pub use rules::pycodestyle::rules::{IOError, SyntaxError}; diff --git a/crates/ruff/src/rule_selector.rs b/crates/ruff/src/rule_selector.rs index 667f6b430acf2..910cac9cb1fa2 100644 --- a/crates/ruff/src/rule_selector.rs +++ b/crates/ruff/src/rule_selector.rs @@ -336,13 +336,14 @@ pub enum Specificity { } #[cfg(feature = "clap")] -mod clap_completion { +pub mod clap_completion { use clap::builder::{PossibleValue, TypedValueParser, ValueParserFactory}; use strum::IntoEnumIterator; use crate::{ codes::RuleCodePrefix, registry::{Linter, RuleNamespace}, + rule_selector::is_single_rule_selector, RuleSelector, }; @@ -362,17 +363,29 @@ mod clap_completion { fn parse_ref( &self, - _cmd: &clap::Command, - _arg: Option<&clap::Arg>, + 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)) + value.parse().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 + '_>> { @@ -387,27 +400,34 @@ mod clap_completion { RuleCodePrefix::iter() // Filter out rule gated behind `#[cfg(feature = "unreachable-code")]`, which is // off-by-default - .filter(|p| { - format!("{}{}", p.linter().common_prefix(), p.short_code()) - != "RUF014" + .filter(|prefix| { + format!( + "{}{}", + prefix.linter().common_prefix(), + prefix.short_code() + ) != "RUF014" }) - .map(|p| { - let prefix = p.linter().common_prefix(); - let code = p.short_code(); - - let mut rules_iter = p.rules(); - 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 + .filter_map(|prefix| { + // Ex) `UP` + if prefix.short_code().is_empty() { + let code = prefix.linter().common_prefix(); + let name = prefix.linter().name(); + return Some(PossibleValue::new(code).help(name)); } + + // Ex) `UP004` + if is_single_rule_selector(&prefix) { + let rule = prefix.rules().next()?; + let code = format!( + "{}{}", + prefix.linter().common_prefix(), + prefix.short_code() + ); + let name: &'static str = rule.into(); + return Some(PossibleValue::new(code).help(name)); + } + + None }), ), ), diff --git a/crates/ruff_cli/src/args.rs b/crates/ruff_cli/src/args.rs index 18ce203ec0f5b..6ff4362eef5a2 100644 --- a/crates/ruff_cli/src/args.rs +++ b/crates/ruff_cli/src/args.rs @@ -1,17 +1,16 @@ use std::path::PathBuf; -use std::str::FromStr; use clap::{command, Parser}; use regex::Regex; -use ruff::line_width::LineLength; use rustc_hash::FxHashMap; +use ruff::line_width::LineLength; use ruff::logging::LogLevel; use ruff::registry::Rule; use ruff::settings::types::{ FilePattern, PatternPrefixPair, PerFileIgnore, PreviewMode, PythonVersion, SerializationFormat, }; -use ruff::RuleSelector; +use ruff::{RuleSelector, RuleSelectorParser}; use ruff_workspace::configuration::{Configuration, RuleSelection}; use ruff_workspace::resolver::ConfigProcessor; @@ -129,7 +128,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -139,7 +138,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -149,7 +148,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -159,7 +158,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide = true )] @@ -191,7 +190,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -201,7 +200,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -211,7 +210,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide_possible_values = true )] @@ -221,7 +220,7 @@ pub struct CheckCommand { long, value_delimiter = ',', value_name = "RULE_CODE", - value_parser = parse_rule_selector, + value_parser = RuleSelectorParser, help_heading = "Rule selection", hide = true )] @@ -511,11 +510,6 @@ impl FormatCommand { } } -fn parse_rule_selector(env: &str) -> Result { - RuleSelector::from_str(env) - .map_err(|e| std::io::Error::new(std::io::ErrorKind::InvalidInput, e)) -} - fn resolve_bool_arg(yes: bool, no: bool) -> Option { match (yes, no) { (true, false) => Some(true), diff --git a/crates/ruff_cli/tests/integration_test.rs b/crates/ruff_cli/tests/integration_test.rs index 4b2ff8d9ea873..79e0ffeb567e9 100644 --- a/crates/ruff_cli/tests/integration_test.rs +++ b/crates/ruff_cli/tests/integration_test.rs @@ -451,7 +451,7 @@ fn preview_group_selector() { ----- stdout ----- ----- stderr ----- - error: invalid value 'PREVIEW' for '--select ': Unknown rule selector: `PREVIEW` + error: invalid value 'PREVIEW' for '--select ' For more information, try '--help'. "###); @@ -470,7 +470,7 @@ fn preview_enabled_group_ignore() { ----- stdout ----- ----- stderr ----- - error: invalid value 'PREVIEW' for '--ignore ': Unknown rule selector: `PREVIEW` + error: invalid value 'PREVIEW' for '--ignore ' For more information, try '--help'. "###); diff --git a/docs/configuration.md b/docs/configuration.md index b210376bfbfa4..fa546ce3b6046 100644 --- a/docs/configuration.md +++ b/docs/configuration.md @@ -476,7 +476,7 @@ Ruff supports two command-line flags that alter its exit code behavior: `--exit-non-zero-on-fix` can result in a non-zero exit code even if no violations remain after autofixing. -## Autocompletion +## Shell autocompletion Ruff supports autocompletion for most shells. A shell-specific completion script can be generated by `ruff generate-shell-completion `, where `` is one of `bash`, `elvish`, `fig`, `fish`,