Skip to content

Commit

Permalink
Propagate attribute-like macros in define_rule_mapping
Browse files Browse the repository at this point in the history
  • Loading branch information
charliermarsh committed Feb 5, 2023
1 parent 84be1df commit 3d0fa72
Show file tree
Hide file tree
Showing 2 changed files with 62 additions and 30 deletions.
43 changes: 27 additions & 16 deletions ruff_macros/src/define_rule_mapping.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ use std::collections::HashMap;
use proc_macro2::Span;
use quote::quote;
use syn::parse::Parse;
use syn::{Ident, LitStr, Path, Token};
use syn::{Attribute, Ident, LitStr, Path, Token};

pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let mut rule_variants = quote!();
Expand All @@ -18,25 +18,32 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let mut diagkind_commit_match_arms = quote!();
let mut from_impls_for_diagkind = quote!();

for (code, path, name) in &mapping.entries {
for (code, path, name, attr) in &mapping.entries {
let code_str = LitStr::new(&code.to_string(), Span::call_site());
rule_variants.extend(quote! {
#[doc = #code_str]
#(#attr)*
#name,
});
diagkind_variants.extend(quote! {#name(#path),});
diagkind_variants.extend(quote! {#(#attr)* #name(#path),});

// Apply the `attrs` to each arm, like `[cfg(feature = "foo")]`.
rule_message_formats_match_arms
.extend(quote! {Self::#name => <#path as Violation>::message_formats(),});
rule_autofixable_match_arms.extend(quote! {Self::#name => <#path as Violation>::AUTOFIX,});
rule_code_match_arms.extend(quote! {Self::#name => #code_str,});
rule_from_code_match_arms.extend(quote! {#code_str => Ok(Rule::#name), });
diagkind_code_match_arms.extend(quote! {Self::#name(..) => &Rule::#name, });
diagkind_body_match_arms.extend(quote! {Self::#name(x) => Violation::message(x), });
.extend(quote! {#(#attr)* Self::#name => <#path as Violation>::message_formats(),});
rule_autofixable_match_arms
.extend(quote! {#(#attr)* Self::#name => <#path as Violation>::AUTOFIX,});
rule_code_match_arms.extend(quote! {#(#attr)* Self::#name => #code_str,});
rule_from_code_match_arms.extend(quote! {#(#attr)* #code_str => Ok(Rule::#name), });
diagkind_code_match_arms.extend(quote! {#(#attr)* Self::#name(..) => &Rule::#name, });
diagkind_body_match_arms
.extend(quote! {#(#attr)* Self::#name(x) => Violation::message(x), });
diagkind_fixable_match_arms
.extend(quote! {Self::#name(x) => x.autofix_title_formatter().is_some(),});
diagkind_commit_match_arms
.extend(quote! {Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), });
.extend(quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().is_some(),});
diagkind_commit_match_arms.extend(
quote! {#(#attr)* Self::#name(x) => x.autofix_title_formatter().map(|f| f(x)), },
);
from_impls_for_diagkind.extend(quote! {
#(#attr)*
impl From<#path> for DiagnosticKind {
fn from(x: #path) -> Self {
DiagnosticKind::#name(x)
Expand All @@ -48,14 +55,15 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
let code_to_name: HashMap<_, _> = mapping
.entries
.iter()
.map(|(code, _, name)| (code.to_string(), name))
.map(|(code, _, name, _)| (code.to_string(), name))
.collect();

let rulecodeprefix = super::rule_code_prefix::expand(
&Ident::new("Rule", Span::call_site()),
&Ident::new("RuleCodePrefix", Span::call_site()),
mapping.entries.iter().map(|(code, ..)| code),
|code| code_to_name[code],
mapping.entries.iter().map(|(.., attr)| attr),
);

quote! {
Expand Down Expand Up @@ -104,7 +112,6 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
}
}


impl DiagnosticKind {
/// The rule of the diagnostic.
pub fn rule(&self) -> &'static Rule {
Expand Down Expand Up @@ -134,19 +141,23 @@ pub fn define_rule_mapping(mapping: &Mapping) -> proc_macro2::TokenStream {
}

pub struct Mapping {
entries: Vec<(Ident, Path, Ident)>,
entries: Vec<(Ident, Path, Ident, Vec<Attribute>)>,
}

impl Parse for Mapping {
fn parse(input: syn::parse::ParseStream) -> syn::Result<Self> {
let mut entries = Vec::new();
while !input.is_empty() {
// Grab the `#[cfg(...)]` attributes.
let attrs = input.call(Attribute::parse_outer)?;

// Parse the `RuleCodePrefix::... => ...` part.
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));
entries.push((code, path, name, attrs));
}
Ok(Self { entries })
}
Expand Down
49 changes: 35 additions & 14 deletions ruff_macros/src/rule_code_prefix.rs
Original file line number Diff line number Diff line change
@@ -1,22 +1,23 @@
use std::collections::{BTreeMap, BTreeSet};
use std::collections::BTreeMap;

use proc_macro2::Span;
use quote::quote;
use syn::Ident;
use syn::{Attribute, Ident};

pub fn expand<'a>(
rule_type: &Ident,
prefix_ident: &Ident,
variants: impl Iterator<Item = &'a Ident>,
variant_name: impl Fn(&str) -> &'a Ident,
attr: impl Iterator<Item = &'a Vec<Attribute>>,
) -> proc_macro2::TokenStream {
// Build up a map from prefix to matching RuleCodes.
let mut prefix_to_codes: BTreeMap<String, BTreeSet<String>> = BTreeMap::default();
let mut prefix_to_codes: BTreeMap<String, BTreeMap<String, Vec<Attribute>>> =
BTreeMap::default();

let mut all_codes = BTreeSet::new();
let mut pl_codes = BTreeSet::new();
let mut pl_codes = BTreeMap::new();

for variant in variants {
for (variant, attr) in variants.zip(attr) {
let code_str = variant.to_string();
let code_prefix_len = code_str
.chars()
Expand All @@ -28,19 +29,25 @@ pub fn expand<'a>(
prefix_to_codes
.entry(prefix)
.or_default()
.insert(code_str.clone());
.entry(code_str.clone())
.or_insert_with(|| attr.clone());
}
if code_str.starts_with("PL") {
pl_codes.insert(code_str.to_string());
pl_codes.insert(code_str, attr.clone());
}
all_codes.insert(code_str);
}

prefix_to_codes.insert("PL".to_string(), pl_codes);

let prefix_variants = prefix_to_codes.keys().map(|prefix| {
let prefix_variants = prefix_to_codes.iter().map(|(prefix, codes)| {
let prefix = Ident::new(prefix, Span::call_site());
let attr = if codes.len() == 1 {
codes.values().next().unwrap().clone()
} else {
vec![]
};
quote! {
#(#attr)*
#prefix
}
});
Expand Down Expand Up @@ -74,24 +81,32 @@ pub fn expand<'a>(
fn generate_impls<'a>(
rule_type: &Ident,
prefix_ident: &Ident,
prefix_to_codes: &BTreeMap<String, BTreeSet<String>>,
prefix_to_codes: &BTreeMap<String, BTreeMap<String, Vec<Attribute>>>,
variant_name: impl Fn(&str) -> &'a Ident,
) -> proc_macro2::TokenStream {
let into_iter_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| {
let codes = codes.iter().map(|code| {
let prefix = Ident::new(prefix_str, Span::call_site());
let attr = if codes.len() == 1 {
codes.values().next().unwrap().clone()
} else {
vec![]
};

let codes = codes.iter().map(|(code, attr)| {
let rule_variant = variant_name(code);
quote! {
#(#attr)*
#rule_type::#rule_variant
}
});
let prefix = Ident::new(prefix_str, Span::call_site());

quote! {
#(#attr)*
#prefix_ident::#prefix => vec![#(#codes),*].into_iter(),
}
});

let specificity_match_arms = prefix_to_codes.keys().map(|prefix_str| {
let specificity_match_arms = prefix_to_codes.iter().map(|(prefix_str, codes)| {
let prefix = Ident::new(prefix_str, Span::call_site());
let mut num_numeric = prefix_str.chars().filter(|char| char.is_numeric()).count();
if prefix_str != "PL" && prefix_str.starts_with("PL") {
Expand All @@ -106,7 +121,13 @@ fn generate_impls<'a>(
5 => quote! { Specificity::Code5Chars },
_ => panic!("Invalid prefix: {prefix}"),
};
let attr = if codes.len() == 1 {
codes.values().next().unwrap().clone()
} else {
vec![]
};
quote! {
#(#attr)*
#prefix_ident::#prefix => #suffix_len,
}
});
Expand Down

0 comments on commit 3d0fa72

Please sign in to comment.