diff --git a/ruff.schema.json b/ruff.schema.json index cfaba7b93f..1a96fc70f2 100644 --- a/ruff.schema.json +++ b/ruff.schema.json @@ -1188,13 +1188,13 @@ "RuleSelector": { "type": "string", "enum": [ + "ALL", "A", "A0", "A00", "A001", "A002", "A003", - "ALL", "ANN", "ANN0", "ANN00", diff --git a/ruff_macros/src/rule_code_prefix.rs b/ruff_macros/src/rule_code_prefix.rs index a91c5fab72..06aeb69b56 100644 --- a/ruff_macros/src/rule_code_prefix.rs +++ b/ruff_macros/src/rule_code_prefix.rs @@ -5,8 +5,6 @@ use proc_macro2::Span; use quote::quote; use syn::Ident; -const ALL: &str = "ALL"; - /// A hash map from deprecated `RuleSelector` to latest /// `RuleSelector`. pub static PREFIX_REDIRECTS: Lazy> = Lazy::new(|| { @@ -116,7 +114,6 @@ pub fn expand<'a>( all_codes.insert(code_str); } - prefix_to_codes.insert(ALL.to_string(), all_codes); prefix_to_codes.insert("PL".to_string(), pl_codes); // Add any prefix aliases (e.g., "U" to "UP"). @@ -148,6 +145,7 @@ pub fn expand<'a>( quote! { #[derive( + ::strum_macros::EnumIter, ::strum_macros::EnumString, ::strum_macros::AsRefStr, Debug, @@ -158,7 +156,6 @@ pub fn expand<'a>( Clone, ::serde::Serialize, ::serde::Deserialize, - ::schemars::JsonSchema, )] pub enum #prefix_ident { #(#prefix_variants,)* @@ -207,27 +204,21 @@ fn generate_impls<'a>( let specificity_match_arms = prefix_to_codes.keys().map(|prefix_str| { let prefix = Ident::new(prefix_str, Span::call_site()); - if prefix_str == ALL { - quote! { - #prefix_ident::#prefix => Specificity::All, - } - } else { - let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count(); - if prefix_str != "PL" && prefix_str.starts_with("PL") { - num_numeric += 1; - } - let suffix_len = match num_numeric { - 0 => quote! { Specificity::Linter }, - 1 => quote! { Specificity::Code1Char }, - 2 => quote! { Specificity::Code2Chars }, - 3 => quote! { Specificity::Code3Chars }, - 4 => quote! { Specificity::Code4Chars }, - 5 => quote! { Specificity::Code5Chars }, - _ => panic!("Invalid prefix: {prefix}"), - }; - quote! { - #prefix_ident::#prefix => #suffix_len, - } + let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count(); + if prefix_str != "PL" && prefix_str.starts_with("PL") { + num_numeric += 1; + } + let suffix_len = match num_numeric { + 0 => quote! { Specificity::Linter }, + 1 => quote! { Specificity::Code1Char }, + 2 => quote! { Specificity::Code2Chars }, + 3 => quote! { Specificity::Code3Chars }, + 4 => quote! { Specificity::Code4Chars }, + 5 => quote! { Specificity::Code5Chars }, + _ => panic!("Invalid prefix: {prefix}"), + }; + quote! { + #prefix_ident::#prefix => #suffix_len, } }); diff --git a/src/rule_selector.rs b/src/rule_selector.rs index 7074d91197..8167bb991e 100644 --- a/src/rule_selector.rs +++ b/src/rule_selector.rs @@ -1,33 +1,120 @@ use std::str::FromStr; +use schemars::_serde_json::Value; +use schemars::schema::{InstanceType, Schema, SchemaObject}; use schemars::JsonSchema; +use serde::de::{self, Visitor}; use serde::{Deserialize, Serialize}; +use strum::IntoEnumIterator; -use crate::registry::{Rule, RuleCodePrefix}; +use crate::registry::{Rule, RuleCodePrefix, RuleIter}; -#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)] -pub struct RuleSelector(RuleCodePrefix); +#[derive(Debug, Clone, PartialEq, Eq, PartialOrd, Ord)] +pub enum RuleSelector { + /// All rules + All, + Prefix(RuleCodePrefix), +} impl FromStr for RuleSelector { - type Err = strum::ParseError; + type Err = ParseError; fn from_str(s: &str) -> Result { - Ok(Self(RuleCodePrefix::from_str(s)?)) + if s == "ALL" { + Ok(Self::All) + } else { + Ok(Self::Prefix( + RuleCodePrefix::from_str(s).map_err(|_| ParseError::Unknown(s.to_string()))?, + )) + } + } +} + +#[derive(Debug, thiserror::Error)] +pub enum ParseError { + #[error("Unknown rule selector `{0}`")] + // TODO(martin): tell the user how to discover rule codes via the CLI once such a command is + // implemented (but that should of course be done only in ruff_cli and not here) + Unknown(String), +} + +impl Serialize for RuleSelector { + fn serialize(&self, serializer: S) -> Result + where + S: serde::Serializer, + { + match self { + RuleSelector::All => serializer.serialize_str("ALL"), + RuleSelector::Prefix(prefix) => prefix.serialize(serializer), + } + } +} + +impl<'de> Deserialize<'de> for RuleSelector { + fn deserialize(deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + // We are not simply doing: + // let s: &str = Deserialize::deserialize(deserializer)?; + // FromStr::from_str(s).map_err(de::Error::custom) + // here because the toml crate apparently doesn't support that + // (as of toml v0.6.0 running `cargo test` failed with the above two lines) + deserializer.deserialize_str(SelectorVisitor) + } +} + +struct SelectorVisitor; + +impl Visitor<'_> for SelectorVisitor { + type Value = RuleSelector; + + fn expecting(&self, formatter: &mut std::fmt::Formatter) -> std::fmt::Result { + formatter.write_str( + "expected a string code identifying a linter or specific rule, or a partial rule code \ + or ALL to refer to all rules", + ) + } + + fn visit_str(self, v: &str) -> Result + where + E: de::Error, + { + FromStr::from_str(v).map_err(de::Error::custom) } } impl From for RuleSelector { fn from(prefix: RuleCodePrefix) -> Self { - Self(prefix) + Self::Prefix(prefix) } } impl IntoIterator for &RuleSelector { - type IntoIter = ::std::vec::IntoIter; + type IntoIter = RuleSelectorIter; type Item = Rule; fn into_iter(self) -> Self::IntoIter { - self.0.into_iter() + match self { + RuleSelector::All => RuleSelectorIter::All(Rule::iter()), + RuleSelector::Prefix(prefix) => RuleSelectorIter::Prefix(prefix.into_iter()), + } + } +} + +pub enum RuleSelectorIter { + All(RuleIter), + Prefix(std::vec::IntoIter), +} + +impl Iterator for RuleSelectorIter { + type Item = Rule; + + fn next(&mut self) -> Option { + match self { + RuleSelectorIter::All(iter) => iter.next(), + RuleSelectorIter::Prefix(iter) => iter.next(), + } } } @@ -37,7 +124,7 @@ impl IntoIterator for &RuleSelector { // RuleSelector` (see https://github.com/rust-lang/rust/issues/67792). // TODO(martin): Remove once RuleSelector is an enum with Linter & Rule variants pub(crate) const fn prefix_to_selector(prefix: RuleCodePrefix) -> RuleSelector { - RuleSelector(prefix) + RuleSelector::Prefix(prefix) } impl JsonSchema for RuleSelector { @@ -45,14 +132,26 @@ impl JsonSchema for RuleSelector { "RuleSelector".to_string() } - fn json_schema(gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { - ::json_schema(gen) + fn json_schema(_gen: &mut schemars::gen::SchemaGenerator) -> schemars::schema::Schema { + Schema::Object(SchemaObject { + instance_type: Some(InstanceType::String.into()), + enum_values: Some( + std::iter::once("ALL".to_string()) + .chain(RuleCodePrefix::iter().map(|s| s.as_ref().to_string())) + .map(Value::String) + .collect(), + ), + ..SchemaObject::default() + }) } } impl RuleSelector { pub(crate) fn specificity(&self) -> Specificity { - self.0.specificity() + match self { + RuleSelector::All => Specificity::All, + RuleSelector::Prefix(prefix) => prefix.specificity(), + } } } diff --git a/src/settings/mod.rs b/src/settings/mod.rs index efea2864c2..1d220da0ed 100644 --- a/src/settings/mod.rs +++ b/src/settings/mod.rs @@ -239,10 +239,7 @@ impl From<&Configuration> for RuleTable { let mut rules = RuleTable::empty(); let fixable = resolve_codes([RuleCodeSpec { - select: config - .fixable - .as_deref() - .unwrap_or(&[crate::registry::RuleCodePrefix::ALL.into()]), + select: config.fixable.as_deref().unwrap_or(&[RuleSelector::All]), ignore: config.unfixable.as_deref().unwrap_or_default(), }]);