diff --git a/ruff_dev/src/generate_rules_table.rs b/ruff_dev/src/generate_rules_table.rs index e0b2b7bbac..65ddcbae25 100644 --- a/ruff_dev/src/generate_rules_table.rs +++ b/ruff_dev/src/generate_rules_table.rs @@ -27,7 +27,11 @@ fn generate_table(table_out: &mut String, prefix: &RuleCodePrefix) { table_out.push('\n'); for rule in prefix.codes() { let kind = rule.kind(); - let fix_token = if kind.fixable() { "🛠" } else { "" }; + let fix_token = match rule.autofixable() { + None => "", + Some(_) => "🛠", + }; + table_out.push_str(&format!( "| {} | {} | {} | {} |", rule.code(), diff --git a/ruff_macros/src/define_rule_mapping.rs b/ruff_macros/src/define_rule_mapping.rs index 006360b103..2e3486894f 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_variants = quote!(); let mut diagkind_variants = quote!(); let mut rule_kind_match_arms = quote!(); + let mut rule_autofixable_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!(); @@ -28,6 +29,7 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { rule_kind_match_arms.extend( quote! {Self::#name => DiagnosticKind::#name(<#path as Violation>::placeholder()),}, ); + rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,}); let origin = get_origin(code); rule_origin_match_arms.extend(quote! {Self::#name => RuleOrigin::#origin,}); rule_code_match_arms.extend(quote! {Self::#name => #code_str,}); @@ -89,6 +91,10 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream { match self { #rule_kind_match_arms } } + pub fn autofixable(&self) -> Option { + match self { #rule_autofixable_match_arms } + } + pub fn origin(&self) -> RuleOrigin { match self { #rule_origin_match_arms } } diff --git a/src/lib.rs b/src/lib.rs index e2b5cbe333..9661eea822 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -47,6 +47,7 @@ mod violations; mod visibility; use cfg_if::cfg_if; +pub use violation::{AutofixKind, Availability as AutofixAvailability}; pub use violations::IOError; cfg_if! { diff --git a/src/violation.rs b/src/violation.rs index 2f7d3204d4..48036c60e1 100644 --- a/src/violation.rs +++ b/src/violation.rs @@ -4,6 +4,10 @@ use serde::de::DeserializeOwned; use serde::Serialize; pub trait Violation: Debug + PartialEq + Eq + Serialize + DeserializeOwned { + /// `None` in the case an autofix is never available or otherwise Some + /// [`AutofixKind`] describing the available autofix. + const AUTOFIX: Option = None; + /// The message used to describe the violation. fn message(&self) -> String; @@ -18,6 +22,21 @@ pub trait Violation: Debug + PartialEq + Eq + Serialize + DeserializeOwned { fn placeholder() -> Self; } +pub struct AutofixKind { + pub available: Availability, +} + +pub enum Availability { + Sometimes, + Always, +} + +impl AutofixKind { + pub const fn new(available: Availability) -> Self { + Self { available } + } +} + /// This trait exists just to make implementing the [`Violation`] trait more /// convenient for violations that can always be autofixed. pub trait AlwaysAutofixableViolation: @@ -35,6 +54,8 @@ pub trait AlwaysAutofixableViolation: /// A blanket implementation. impl Violation for VA { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { ::message(self) } diff --git a/src/violations.rs b/src/violations.rs index 74dbce7cc2..3056fbc156 100644 --- a/src/violations.rs +++ b/src/violations.rs @@ -11,7 +11,7 @@ use crate::rules::flake8_pytest_style::types::{ }; use crate::rules::flake8_quotes::settings::Quote; use crate::rules::pyupgrade::types::Primitive; -use crate::violation::{AlwaysAutofixableViolation, Violation}; +use crate::violation::{AlwaysAutofixableViolation, AutofixKind, Violation, Availability}; // pycodestyle errors @@ -342,6 +342,8 @@ fn fmt_unused_import_autofix_msg(unused_import: &UnusedImport) -> String { } } impl Violation for UnusedImport { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { let UnusedImport(name, ignore_init, ..) = self; if *ignore_init { @@ -684,6 +686,8 @@ define_violation!( pub struct MultiValueRepeatedKeyLiteral(pub String, pub bool); ); impl Violation for MultiValueRepeatedKeyLiteral { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { let MultiValueRepeatedKeyLiteral(name, ..) = self; format!("Dictionary key literal `{name}` repeated") @@ -709,6 +713,8 @@ define_violation!( pub struct MultiValueRepeatedKeyVariable(pub String, pub bool); ); impl Violation for MultiValueRepeatedKeyVariable { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { let MultiValueRepeatedKeyVariable(name, ..) = self; format!("Dictionary key `{name}` repeated") @@ -3502,6 +3508,8 @@ define_violation!( } ); impl Violation for DatetimeTimezoneUTC { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { "Use `datetime.UTC` alias".to_string() } @@ -4010,6 +4018,8 @@ fn fmt_blank_line_after_summary_autofix_msg(_: &BlankLineAfterSummary) -> String "Insert single blank line".to_string() } impl Violation for BlankLineAfterSummary { + const AUTOFIX: Option = Some(AutofixKind::new(Availability::Always)); + fn message(&self) -> String { let BlankLineAfterSummary(num_lines) = self; if *num_lines == 0 {