Convert define_rule_mapping! to a procedural macro

define_rule_mapping! was previously implemented as a declarative macro,
which was however partially relying on an origin_by_code! proc macro
because declarative macros cannot match on substrings of identifiers.

Currently all define_rule_mapping! lines look like the following:

    TID251 => violations::BannedApi,
    TID252 => violations::BannedRelativeImport,

We want to break up violations.rs, moving the violation definitions to
the respective rule modules. To do this we want to change the previous
lines to:

    TID251 => rules::flake8_tidy_imports::banned_api::BannedApi,
    TID252 => rules::flake8_tidy_imports::relative_imports::RelativeImport,

This however doesn't work because the define_rule_mapping! macro is
currently defined as:

    ($($code:ident => $mod:ident::$name:ident,)+) => { ... }

That is it only supported $module::$name but not longer paths with
multiple modules. While we could define `=> $path:path`[1] then we
could no longer access the last path segment, which we need because
we use it for the DiagnosticKind variant names. And
`$path:path::$last:ident` doesn't work either because it would be
ambiguous (Rust wouldn't know where the path ends ... so path fragments
have to be followed by some punctuation/keyword that may not be part of
paths). And we also cannot just introduce a procedural macro like
path_basename!(...) because the following is not valid Rust code:

    enum Foo { foo!(...), }

(macros cannot be called in the place where you define variants.)

So we have to convert define_rule_mapping! into a proc macro in order to
support paths of arbitrary length and this commit implements that.

[1]: https://doc.rust-lang.org/reference/macros-by-example.html#metavariables
This commit is contained in:
Martin Fischer
2023-01-15 05:54:33 +01:00
committed by Charlie Marsh
parent e3cc918b93
commit 81996f1bcc
3 changed files with 139 additions and 120 deletions

View File

@@ -15,106 +15,7 @@ use crate::fix::Fix;
use crate::violation::Violation;
use crate::violations;
macro_rules! define_rule_mapping {
($($code:ident => $mod:ident::$name:ident,)+) => {
#[derive(
AsRefStr,
RuleCodePrefix,
EnumIter,
EnumString,
Debug,
Display,
PartialEq,
Eq,
Clone,
Serialize,
Deserialize,
Hash,
PartialOrd,
Ord,
)]
pub enum RuleCode {
$(
$code,
)+
}
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
pub enum DiagnosticKind {
$(
$name($mod::$name),
)+
}
impl RuleCode {
/// A placeholder representation of the `DiagnosticKind` for the diagnostic.
pub fn kind(&self) -> DiagnosticKind {
match self {
$(
RuleCode::$code => DiagnosticKind::$name(<$mod::$name as Violation>::placeholder()),
)+
}
}
pub fn origin(&self) -> RuleOrigin {
match self {
$(
RuleCode::$code => ruff_macros::origin_by_code!($code),
)+
}
}
}
impl DiagnosticKind {
/// A four-letter shorthand code for the diagnostic.
pub fn code(&self) -> &'static RuleCode {
match self {
$(
DiagnosticKind::$name(..) => &RuleCode::$code,
)+
}
}
/// The body text for the diagnostic.
pub fn body(&self) -> String {
match self {
$(
DiagnosticKind::$name(x) => Violation::message(x),
)+
}
}
/// Whether the diagnostic is (potentially) fixable.
pub fn fixable(&self) -> bool {
match self {
$(
DiagnosticKind::$name(x) => x.autofix_title_formatter().is_some(),
)+
}
}
/// The message used to describe the fix action for a given `DiagnosticKind`.
pub fn commit(&self) -> Option<String> {
match self {
$(
DiagnosticKind::$name(x) => x.autofix_title_formatter().map(|f| f(x)),
)+
}
}
}
$(
impl From<$mod::$name> for DiagnosticKind {
fn from(x: $mod::$name) -> Self {
DiagnosticKind::$name(x)
}
}
)+
};
}
define_rule_mapping!(
ruff_macros::define_rule_mapping!(
// pycodestyle errors
E401 => violations::MultipleImportsOnOneLine,
E402 => violations::ModuleImportNotAtTopOfFile,