mirror of
https://github.com/astral-sh/ruff.git
synced 2025-07-12 15:45:21 +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)]
|
#![forbid(unsafe_code)]
|
||||||
|
|
||||||
use proc_macro2::Span;
|
use syn::{parse_macro_input, DeriveInput};
|
||||||
use quote::quote;
|
|
||||||
use syn::{parse_macro_input, DeriveInput, Ident};
|
|
||||||
|
|
||||||
mod config;
|
mod config;
|
||||||
|
mod define_rule_mapping;
|
||||||
mod prefixes;
|
mod prefixes;
|
||||||
mod rule_code_prefix;
|
mod rule_code_prefix;
|
||||||
|
|
||||||
|
@ -40,21 +39,7 @@ pub fn derive_rule_code_prefix(input: proc_macro::TokenStream) -> proc_macro::To
|
||||||
}
|
}
|
||||||
|
|
||||||
#[proc_macro]
|
#[proc_macro]
|
||||||
pub fn origin_by_code(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
pub fn define_rule_mapping(item: proc_macro::TokenStream) -> proc_macro::TokenStream {
|
||||||
let ident = parse_macro_input!(item as Ident).to_string();
|
let mapping = parse_macro_input!(item as define_rule_mapping::Mapping);
|
||||||
let mut iter = prefixes::PREFIX_TO_ORIGIN.iter();
|
define_rule_mapping::define_rule_mapping(mapping).into()
|
||||||
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()
|
|
||||||
}
|
}
|
||||||
|
|
101
src/registry.rs
101
src/registry.rs
|
@ -15,106 +15,7 @@ use crate::fix::Fix;
|
||||||
use crate::violation::Violation;
|
use crate::violation::Violation;
|
||||||
use crate::violations;
|
use crate::violations;
|
||||||
|
|
||||||
macro_rules! define_rule_mapping {
|
ruff_macros::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!(
|
|
||||||
// pycodestyle errors
|
// pycodestyle errors
|
||||||
E401 => violations::MultipleImportsOnOneLine,
|
E401 => violations::MultipleImportsOnOneLine,
|
||||||
E402 => violations::ModuleImportNotAtTopOfFile,
|
E402 => violations::ModuleImportNotAtTopOfFile,
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue