structs 0/9: Introduce Violation trait

For every available rule registry.rs currently defines:

1. A CheckCode variant to identify the rule.
2. A CheckKind variant to represent violations of the rule.
3. A mapping from the CheckCode variant to a placeholder CheckKind instance.
4. A mapping from the CheckKind variant to CheckCode.
5. A mapping from the CheckKind to a string description.
6. A mapping from the CheckKind to a boolean indicating if autofix is available.
7. A mapping from the CheckKind to a string describing the autofix if available.

Since registry.rs defines all of this for every single rule and
ruff has hundreds of rules, this results in the lines specific to
a particular rule to be hundreds of lines apart, making the code
cumbersome to read and edit.

This commit introduces a new Violation trait so that the rule-specific
details of the above steps 5.-7. can be defined next to the rule
implementation. The idea is that once all CheckCode/CheckKind variants
have been converted to this new approach then the steps 1.-4. in
registry.rs could simply be generated via a declarative macro, e.g:

    define_rule_mapping!(
        E501 => pycodestyle::LineTooLong,
        ...
    );

(where `pycodestyle::LineTooLong` would be a struct that implements the
new Violation trait).

The define_rule_mapping! macro would then take care of generating the
CheckCode and CheckKind enums, as well as all of the implementations for
the previously mentioned mappings, by simply calling the methods of the
new Violation trait.

There is another nice benefit from this approach: We want to introduce
more thorough documentation for rules and we want the documentation of a
rule to be defined close by the implementation (so that it's easier to
check if they match and that they're less likely to become out of sync).
Since these new Violation structs can be defined close by the
implementation of a rule we can also use them as an anchor point for the
documentation of a rule by simply adding the documentation in the form
of a Rust doc comment to the struct.
This commit is contained in:
Martin Fischer 2023-01-06 07:28:05 +01:00 committed by Charlie Marsh
parent 3e80c0d43e
commit eea1379a74
2 changed files with 61 additions and 0 deletions

View File

@ -75,6 +75,7 @@ pub mod source_code_generator;
pub mod source_code_locator;
pub mod source_code_style;
mod vendor;
mod violation;
pub mod visibility;
cfg_if! {

60
src/violation.rs Normal file
View File

@ -0,0 +1,60 @@
use std::fmt::Debug;
use serde::de::DeserializeOwned;
use serde::Serialize;
pub trait Violation: Debug + PartialEq + Eq + Serialize + DeserializeOwned {
/// The message used to describe the violation.
fn message(&self) -> String;
/// If autofix is (potentially) available for this violation returns another
/// function that in turn can be used to obtain a string describing the
/// autofix.
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
None
}
/// A placeholder instance of the violation.
fn placeholder() -> Self;
}
/// This trait exists just to make implementing the [`Violation`] trait more
/// convenient for violations that can always be autofixed.
pub trait AlwaysAutofixableViolation:
Debug + PartialEq + Eq + Serialize + DeserializeOwned
{
/// The message used to describe the violation.
fn message(&self) -> String;
/// The title displayed for the available autofix.
fn autofix_title(&self) -> String;
/// A placeholder instance of the violation.
fn placeholder() -> Self;
}
/// A blanket implementation.
impl<VA: AlwaysAutofixableViolation> Violation for VA {
fn message(&self) -> String {
<Self as AlwaysAutofixableViolation>::message(self)
}
fn autofix_title_formatter(&self) -> Option<fn(&Self) -> String> {
Some(Self::autofix_title)
}
fn placeholder() -> Self {
<Self as AlwaysAutofixableViolation>::placeholder()
}
}
/// This macro just exists so that you don't have to add the `#[derive]`
/// attribute every time you define a new violation. And so that new traits can
/// be easily derived everywhere by just changing a single line.
#[macro_export]
macro_rules! define_violation {
($($struct:tt)*) => {
#[derive(Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
$($struct)*
};
}