mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-09 22:25:20 +00:00
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:
parent
e3cc918b93
commit
81996f1bcc
3 changed files with 139 additions and 120 deletions
133
ruff_macros/src/define_rule_mapping.rs
Normal file
133
ruff_macros/src/define_rule_mapping.rs
Normal file
|
@ -0,0 +1,133 @@
|
|||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::parse::Parse;
|
||||
use syn::{Ident, Path, Token};
|
||||
|
||||
pub fn define_rule_mapping(mapping: Mapping) -> proc_macro2::TokenStream {
|
||||
let mut rulecode_variants = quote!();
|
||||
let mut diagkind_variants = quote!();
|
||||
let mut rulecode_kind_match_arms = quote!();
|
||||
let mut rulecode_origin_match_arms = quote!();
|
||||
let mut diagkind_code_match_arms = quote!();
|
||||
let mut diagkind_body_match_arms = quote!();
|
||||
let mut diagkind_fixable_match_arms = quote!();
|
||||
let mut diagkind_commit_match_arms = quote!();
|
||||
let mut from_impls_for_diagkind = quote!();
|
||||
|
||||
for (code, path, name) in mapping.entries {
|
||||
rulecode_variants.extend(quote! {#code,});
|
||||
diagkind_variants.extend(quote! {#name(#path),});
|
||||
rulecode_kind_match_arms.extend(
|
||||
quote! {RuleCode::#code => DiagnosticKind::#name(<#path as Violation>::placeholder()),},
|
||||
);
|
||||
let origin = get_origin(&code);
|
||||
rulecode_origin_match_arms.extend(quote! {RuleCode::#code => RuleOrigin::#origin,});
|
||||
diagkind_code_match_arms.extend(quote! {DiagnosticKind::#name(..) => &RuleCode::#code, });
|
||||
diagkind_body_match_arms
|
||||
.extend(quote! {DiagnosticKind::#name(x) => Violation::message(x), });
|
||||
diagkind_fixable_match_arms
|
||||
.extend(quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().is_some(),});
|
||||
diagkind_commit_match_arms.extend(
|
||||
quote! {DiagnosticKind::#name(x) => x.autofix_title_formatter().map(|f| f(x)), },
|
||||
);
|
||||
from_impls_for_diagkind.extend(quote! {
|
||||
impl From<#path> for DiagnosticKind {
|
||||
fn from(x: #path) -> Self {
|
||||
DiagnosticKind::#name(x)
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
quote! {
|
||||
#[derive(
|
||||
AsRefStr,
|
||||
RuleCodePrefix,
|
||||
EnumIter,
|
||||
EnumString,
|
||||
Debug,
|
||||
Display,
|
||||
PartialEq,
|
||||
Eq,
|
||||
Clone,
|
||||
Serialize,
|
||||
Deserialize,
|
||||
Hash,
|
||||
PartialOrd,
|
||||
Ord,
|
||||
)]
|
||||
pub enum RuleCode { #rulecode_variants }
|
||||
|
||||
#[derive(AsRefStr, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum DiagnosticKind { #diagkind_variants }
|
||||
|
||||
|
||||
impl RuleCode {
|
||||
/// A placeholder representation of the `DiagnosticKind` for the diagnostic.
|
||||
pub fn kind(&self) -> DiagnosticKind {
|
||||
match self { #rulecode_kind_match_arms }
|
||||
}
|
||||
|
||||
pub fn origin(&self) -> RuleOrigin {
|
||||
match self { #rulecode_origin_match_arms }
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
impl DiagnosticKind {
|
||||
/// A four-letter shorthand code for the diagnostic.
|
||||
pub fn code(&self) -> &'static RuleCode {
|
||||
match self { #diagkind_code_match_arms }
|
||||
}
|
||||
|
||||
/// The body text for the diagnostic.
|
||||
pub fn body(&self) -> String {
|
||||
match self { #diagkind_body_match_arms }
|
||||
}
|
||||
|
||||
/// Whether the diagnostic is (potentially) fixable.
|
||||
pub fn fixable(&self) -> bool {
|
||||
match self { #diagkind_fixable_match_arms }
|
||||
}
|
||||
|
||||
/// The message used to describe the fix action for a given `DiagnosticKind`.
|
||||
pub fn commit(&self) -> Option<String> {
|
||||
match self { #diagkind_commit_match_arms }
|
||||
}
|
||||
}
|
||||
|
||||
#from_impls_for_diagkind
|
||||
}
|
||||
}
|
||||
|
||||
fn get_origin(ident: &Ident) -> Ident {
|
||||
let ident = ident.to_string();
|
||||
let mut iter = crate::prefixes::PREFIX_TO_ORIGIN.iter();
|
||||
let origin = loop {
|
||||
let (prefix, origin) = iter
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("code doesn't start with any recognized prefix: {ident}"));
|
||||
if ident.starts_with(prefix) {
|
||||
break origin;
|
||||
}
|
||||
};
|
||||
Ident::new(origin, Span::call_site())
|
||||
}
|
||||
pub struct Mapping {
|
||||
entries: Vec<(Ident, Path, Ident)>,
|
||||
}
|
||||
|
||||
impl Parse for Mapping {
|
||||
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
|
||||
let mut entries = Vec::new();
|
||||
while !input.is_empty() {
|
||||
let code: Ident = input.parse()?;
|
||||
let _: Token![=>] = input.parse()?;
|
||||
let path: Path = input.parse()?;
|
||||
let name = path.segments.last().unwrap().ident.clone();
|
||||
let _: Token![,] = input.parse()?;
|
||||
entries.push((code, path, name));
|
||||
}
|
||||
Ok(Mapping { entries })
|
||||
}
|
||||
}
|
|
@ -13,11 +13,10 @@
|
|||
)]
|
||||
#![forbid(unsafe_code)]
|
||||
|
||||
use proc_macro2::Span;
|
||||
use quote::quote;
|
||||
use syn::{parse_macro_input, DeriveInput, Ident};
|
||||
use syn::{parse_macro_input, DeriveInput};
|
||||
|
||||
mod config;
|
||||
mod define_rule_mapping;
|
||||
mod prefixes;
|
||||
mod rule_code_prefix;
|
||||
|
||||
|
@ -40,21 +39,7 @@ pub fn derive_rule_code_prefix(input: proc_macro::TokenStream) -> proc_macro::To
|
|||
}
|
||||
|
||||
#[proc_macro]
|
||||
pub fn origin_by_code(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let ident = parse_macro_input!(item as Ident).to_string();
|
||||
let mut iter = prefixes::PREFIX_TO_ORIGIN.iter();
|
||||
let origin = loop {
|
||||
let (prefix, origin) = iter
|
||||
.next()
|
||||
.unwrap_or_else(|| panic!("code doesn't start with any recognized prefix: {ident}"));
|
||||
if ident.starts_with(prefix) {
|
||||
break origin;
|
||||
}
|
||||
};
|
||||
let prefix = Ident::new(origin, Span::call_site());
|
||||
|
||||
quote! {
|
||||
RuleOrigin::#prefix
|
||||
}
|
||||
.into()
|
||||
pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||
let mapping = parse_macro_input!(item as define_rule_mapping::Mapping);
|
||||
define_rule_mapping::define_rule_mapping(mapping).into()
|
||||
}
|
||||
|
|
101
src/registry.rs
101
src/registry.rs
|
@ -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,
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue