derive-msg-formats 3/5: Introduce Violation::AUTOFIX associated constant

ruff_dev::generate_rules_table previously documented which rules are
autofixable via DiagnosticKind::fixable ... since the DiagnosticKind was
obtained via Rule::kind (and Violation::placeholder) which we both want
to get rid of we have to obtain the autofixability via another way.

This commit implements such another way by adding an AUTOFIX
associated constant to the Violation trait. The constant is of the type
Option<AutoFixkind>, AutofixKind is a new struct containing an
Availability enum { Sometimes, Always}, letting us additionally document
that some autofixes are only available sometimes (which previously
wasn't documented). We intentionally introduce this information in a
struct so that we can easily introduce further autofix metadata in the
future such as autofix applicability[1].

[1]: https://doc.rust-lang.org/stable/nightly-rustc/rustc_errors/enum.Applicability.html
This commit is contained in:
Martin Fischer 2023-01-19 14:04:59 +01:00 committed by Charlie Marsh
parent 8993baab01
commit 8f6d8e215c
5 changed files with 44 additions and 2 deletions

View File

@ -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(),

View File

@ -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<crate::violation::AutofixKind> {
match self { #rule_autofixable_match_arms }
}
pub fn origin(&self) -> RuleOrigin {
match self { #rule_origin_match_arms }
}

View File

@ -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! {

View File

@ -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<AutofixKind> = 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<VA: AlwaysAutofixableViolation> Violation for VA {
const AUTOFIX: Option<AutofixKind> = Some(AutofixKind::new(Availability::Always));
fn message(&self) -> String {
<Self as AlwaysAutofixableViolation>::message(self)
}

View File

@ -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<AutofixKind> = 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<AutofixKind> = 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<AutofixKind> = 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<AutofixKind> = 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<AutofixKind> = Some(AutofixKind::new(Availability::Always));
fn message(&self) -> String {
let BlankLineAfterSummary(num_lines) = self;
if *num_lines == 0 {