diff --git a/Cargo.lock b/Cargo.lock index ce0875754f..69a73b347d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1951,6 +1951,7 @@ dependencies = [ "strum_macros", "test-case", "textwrap", + "thiserror", "titlecase", "toml_edit", "wasm-bindgen", diff --git a/Cargo.toml b/Cargo.toml index e21e57418d..7ba3866a55 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -61,6 +61,7 @@ strum_macros = { version = "0.24.3" } textwrap = { version = "0.16.0" } titlecase = { version = "2.2.1" } toml_edit = { version = "0.17.1", features = ["easy"] } +thiserror = { version = "1.0" } # https://docs.rs/getrandom/0.2.7/getrandom/#webassembly-support # For (future) wasm-pack support diff --git a/ruff_cli/src/cli.rs b/ruff_cli/src/cli.rs index 358254eecf..f4b5fad5c4 100644 --- a/ruff_cli/src/cli.rs +++ b/ruff_cli/src/cli.rs @@ -4,7 +4,7 @@ use clap::{command, Parser}; use regex::Regex; use ruff::fs; use ruff::logging::LogLevel; -use ruff::registry::{RuleCode, RuleCodePrefix}; +use ruff::registry::{Rule, RuleCodePrefix}; use ruff::resolver::ConfigProcessor; use ruff::settings::types::{ FilePattern, PatternPrefixPair, PerFileIgnore, PythonVersion, SerializationFormat, @@ -169,6 +169,7 @@ pub struct Cli { /// Explain a rule. #[arg( long, + value_parser=Rule::from_code, // Fake subcommands. conflicts_with = "add_noqa", conflicts_with = "clean", @@ -180,7 +181,7 @@ pub struct Cli { conflicts_with = "stdin_filename", conflicts_with = "watch", )] - pub explain: Option, + pub explain: Option, /// Generate shell completion #[arg( long, @@ -302,7 +303,7 @@ pub struct Arguments { pub config: Option, pub diff: bool, pub exit_zero: bool, - pub explain: Option, + pub explain: Option, pub files: Vec, pub generate_shell_completion: Option, pub isolated: bool, diff --git a/ruff_cli/src/main.rs b/ruff_cli/src/main.rs index be163b6c40..101b638105 100644 --- a/ruff_cli/src/main.rs +++ b/ruff_cli/src/main.rs @@ -157,8 +157,8 @@ quoting the executed command, along with the relevant file contents and `pyproje PyprojectDiscovery::Hierarchical(settings) => settings.cli.clone(), }; - if let Some(code) = cli.explain { - commands::explain(&code, format)?; + if let Some(rule) = cli.explain { + commands::explain(&rule, format)?; return Ok(ExitCode::SUCCESS); } if cli.show_settings { diff --git a/ruff_macros/src/define_rule_mapping.rs b/ruff_macros/src/define_rule_mapping.rs index 1826c5a596..5f375fb3b9 100644 --- a/ruff_macros/src/define_rule_mapping.rs +++ b/ruff_macros/src/define_rule_mapping.rs @@ -9,6 +9,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { let mut rule_kind_match_arms = quote!(); let mut rule_origin_match_arms = quote!(); let mut rule_code_match_arms = quote!(); + let mut rule_from_code_match_arms = quote!(); let mut diagkind_code_match_arms = quote!(); let mut diagkind_body_match_arms = quote!(); let mut diagkind_fixable_match_arms = quote!(); @@ -25,6 +26,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { rule_origin_match_arms.extend(quote! {Self::#code => RuleOrigin::#origin,}); let code_str = LitStr::new(&code.to_string(), Span::call_site()); rule_code_match_arms.extend(quote! {Self::#code => #code_str,}); + rule_from_code_match_arms.extend(quote! {#code_str => Ok(&RuleCode::#code), }); diagkind_code_match_arms.extend(quote! {Self::#name(..) => &RuleCode::#code, }); diagkind_body_match_arms.extend(quote! {Self::#name(x) => Violation::message(x), }); diagkind_fixable_match_arms @@ -49,7 +51,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { quote! { #[derive( EnumIter, - EnumString, // TODO(martin): Remove Debug, PartialEq, Eq, @@ -67,6 +68,11 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { #[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)] pub enum DiagnosticKind { #diagkind_variants } + #[derive(thiserror::Error, Debug)] + pub enum FromCodeError { + #[error("unknown rule code")] + Unknown, + } impl Rule { /// A placeholder representation of the `DiagnosticKind` for the diagnostic. @@ -81,6 +87,13 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { pub fn code(&self) -> &'static str { match self { #rule_code_match_arms } } + + pub fn from_code(code: &str) -> Result<&'static Self, FromCodeError> { + match code { + #rule_from_code_match_arms + _ => Err(FromCodeError::Unknown), + } + } } diff --git a/src/checkers/noqa.rs b/src/checkers/noqa.rs index 66e087da3d..0c613dc108 100644 --- a/src/checkers/noqa.rs +++ b/src/checkers/noqa.rs @@ -1,14 +1,12 @@ //! `NoQA` enforcement and validation. -use std::str::FromStr; - use nohash_hasher::IntMap; use rustpython_parser::ast::Location; use crate::ast::types::Range; use crate::fix::Fix; use crate::noqa::{is_file_exempt, Directive}; -use crate::registry::{Diagnostic, DiagnosticKind, RuleCode, CODE_REDIRECTS}; +use crate::registry::{Diagnostic, DiagnosticKind, Rule, RuleCode, CODE_REDIRECTS}; use crate::settings::{flags, Settings}; use crate::violations::UnusedCodes; use crate::{noqa, violations}; @@ -134,8 +132,8 @@ pub fn check_noqa( if matches.contains(&code) || settings.external.contains(code) { valid_codes.push(code); } else { - if let Ok(rule_code) = RuleCode::from_str(code) { - if settings.rules.enabled(&rule_code) { + if let Ok(rule) = Rule::from_code(code) { + if settings.rules.enabled(rule) { unmatched_codes.push(code); } else { disabled_codes.push(code); diff --git a/src/registry.rs b/src/registry.rs index 723610b165..d76f0ecf17 100644 --- a/src/registry.rs +++ b/src/registry.rs @@ -5,7 +5,7 @@ use once_cell::sync::Lazy; use rustc_hash::FxHashMap; use rustpython_parser::ast::Location; use serde::{Deserialize, Serialize}; -use strum_macros::{AsRefStr, EnumIter, EnumString}; +use strum_macros::{AsRefStr, EnumIter}; use crate::ast::types::Range; use crate::fix::Fix; @@ -712,8 +712,6 @@ pub static CODE_REDIRECTS: Lazy> = Lazy::new(| #[cfg(test)] mod tests { - use std::str::FromStr; - use strum::IntoEnumIterator; use crate::registry::Rule; @@ -722,7 +720,7 @@ mod tests { fn check_code_serialization() { for rule in Rule::iter() { assert!( - Rule::from_str(rule.code()).is_ok(), + Rule::from_code(rule.code()).is_ok(), "{rule:?} could not be round-trip serialized." ); }