mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-03 10:22:24 +00:00
refactor: Change RuleNamespace::prefixes to common_prefix
Previously Linter::parse_code("E401") returned (Linter::Pycodestyle, "401") ... after this commit it returns (Linter::Pycodestyle, "E401") instead, which is important for the future implementation of the many-to-many mapping. (The second value of the tuple isn't used currently.)
This commit is contained in:
parent
3ee6a90905
commit
b532fce792
3 changed files with 65 additions and 27 deletions
|
@ -1,6 +1,7 @@
|
|||
//! Generate a Markdown-compatible table of supported lint rules.
|
||||
|
||||
use anyhow::Result;
|
||||
use itertools::Itertools;
|
||||
use ruff::registry::{Linter, LinterCategory, Rule, RuleNamespace};
|
||||
use strum::IntoEnumIterator;
|
||||
|
||||
|
@ -47,7 +48,15 @@ pub fn main(args: &Args) -> Result<()> {
|
|||
let mut table_out = String::new();
|
||||
let mut toc_out = String::new();
|
||||
for linter in Linter::iter() {
|
||||
let codes_csv: String = linter.prefixes().join(", ");
|
||||
let codes_csv: String = match linter.common_prefix() {
|
||||
"" => linter
|
||||
.categories()
|
||||
.unwrap()
|
||||
.iter()
|
||||
.map(|LinterCategory(prefix, ..)| prefix)
|
||||
.join(", "),
|
||||
prefix => prefix.to_string(),
|
||||
};
|
||||
table_out.push_str(&format!("### {} ({codes_csv})", linter.name()));
|
||||
table_out.push('\n');
|
||||
table_out.push('\n');
|
||||
|
|
|
@ -15,13 +15,15 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
|||
|
||||
let mut parsed = Vec::new();
|
||||
|
||||
let mut prefix_match_arms = quote!();
|
||||
let mut common_prefix_match_arms = quote!();
|
||||
let mut name_match_arms = quote!(Self::Ruff => "Ruff-specific rules",);
|
||||
let mut url_match_arms = quote!(Self::Ruff => None,);
|
||||
let mut into_iter_match_arms = quote!();
|
||||
|
||||
let mut all_prefixes = HashSet::new();
|
||||
|
||||
for variant in variants {
|
||||
let mut first_chars = HashSet::new();
|
||||
let prefixes: Result<Vec<_>, _> = variant
|
||||
.attrs
|
||||
.iter()
|
||||
|
@ -33,7 +35,9 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
|||
let str = lit.value();
|
||||
match str.chars().next() {
|
||||
None => return Err(Error::new(lit.span(), "expected prefix string to be non-empty")),
|
||||
Some(_) => {},
|
||||
Some(c) => if !first_chars.insert(c) {
|
||||
return Err(Error::new(lit.span(), format!("this variant already has another prefix starting with the character '{c}'")))
|
||||
}
|
||||
}
|
||||
if !all_prefixes.insert(str.clone()) {
|
||||
return Err(Error::new(lit.span(), "prefix has already been defined before"));
|
||||
|
@ -63,31 +67,43 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
|||
}
|
||||
|
||||
for lit in &prefixes {
|
||||
parsed.push((lit.clone(), variant_ident.clone()));
|
||||
parsed.push((
|
||||
lit.clone(),
|
||||
variant_ident.clone(),
|
||||
match prefixes.len() {
|
||||
1 => ParseStrategy::SinglePrefix,
|
||||
_ => ParseStrategy::MultiplePrefixes,
|
||||
},
|
||||
));
|
||||
}
|
||||
|
||||
prefix_match_arms.extend(quote! {
|
||||
Self::#variant_ident => &[#(#prefixes),*],
|
||||
});
|
||||
if let [prefix] = &prefixes[..] {
|
||||
common_prefix_match_arms.extend(quote! { Self::#variant_ident => #prefix, });
|
||||
|
||||
let prefix_ident = Ident::new(prefix, Span::call_site());
|
||||
into_iter_match_arms.extend(quote! {
|
||||
#ident::#variant_ident => RuleCodePrefix::#prefix_ident.into_iter(),
|
||||
});
|
||||
} else {
|
||||
// There is more than one prefix. We already previously asserted
|
||||
// that prefixes of the same variant don't start with the same character
|
||||
// so the common prefix for this variant is the empty string.
|
||||
common_prefix_match_arms.extend(quote! { Self::#variant_ident => "", });
|
||||
}
|
||||
}
|
||||
|
||||
parsed.sort_by_key(|(prefix, _)| Reverse(prefix.len()));
|
||||
parsed.sort_by_key(|(prefix, ..)| Reverse(prefix.len()));
|
||||
|
||||
let mut if_statements = quote!();
|
||||
let mut into_iter_match_arms = quote!();
|
||||
|
||||
for (prefix, field) in parsed {
|
||||
for (prefix, field, strategy) in parsed {
|
||||
let ret_str = match strategy {
|
||||
ParseStrategy::SinglePrefix => quote!(rest),
|
||||
ParseStrategy::MultiplePrefixes => quote!(code),
|
||||
};
|
||||
if_statements.extend(quote! {if let Some(rest) = code.strip_prefix(#prefix) {
|
||||
return Some((#ident::#field, rest));
|
||||
return Some((#ident::#field, #ret_str));
|
||||
}});
|
||||
|
||||
let prefix_ident = Ident::new(&prefix, Span::call_site());
|
||||
|
||||
if field != "Pycodestyle" {
|
||||
into_iter_match_arms.extend(quote! {
|
||||
#ident::#field => RuleCodePrefix::#prefix_ident.into_iter(),
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
into_iter_match_arms.extend(quote! {
|
||||
|
@ -104,9 +120,8 @@ pub fn derive_impl(input: DeriveInput) -> syn::Result<proc_macro2::TokenStream>
|
|||
None
|
||||
}
|
||||
|
||||
|
||||
fn prefixes(&self) -> &'static [&'static str] {
|
||||
match self { #prefix_match_arms }
|
||||
fn common_prefix(&self) -> &'static str {
|
||||
match self { #common_prefix_match_arms }
|
||||
}
|
||||
|
||||
fn name(&self) -> &'static str {
|
||||
|
@ -152,3 +167,8 @@ fn parse_doc_attr(doc_attr: &Attribute) -> syn::Result<(String, String)> {
|
|||
fn parse_markdown_link(link: &str) -> Option<(&str, &str)> {
|
||||
link.strip_prefix('[')?.strip_suffix(')')?.split_once("](")
|
||||
}
|
||||
|
||||
enum ParseStrategy {
|
||||
SinglePrefix,
|
||||
MultiplePrefixes,
|
||||
}
|
||||
|
|
|
@ -615,9 +615,16 @@ pub enum Linter {
|
|||
}
|
||||
|
||||
pub trait RuleNamespace: Sized {
|
||||
fn parse_code(code: &str) -> Option<(Self, &str)>;
|
||||
/// Returns the prefix that every single code that ruff uses to identify
|
||||
/// rules from this linter starts with. In the case that multiple
|
||||
/// `#[prefix]`es are configured for the variant in the `Linter` enum
|
||||
/// definition this is the empty string.
|
||||
fn common_prefix(&self) -> &'static str;
|
||||
|
||||
fn prefixes(&self) -> &'static [&'static str];
|
||||
/// Attempts to parse the given rule code. If the prefix is recognized
|
||||
/// returns the respective variant along with the code with the common
|
||||
/// prefix stripped.
|
||||
fn parse_code(code: &str) -> Option<(Self, &str)>;
|
||||
|
||||
fn name(&self) -> &'static str;
|
||||
|
||||
|
@ -766,10 +773,12 @@ mod tests {
|
|||
}
|
||||
|
||||
#[test]
|
||||
fn test_linter_prefixes() {
|
||||
fn test_linter_parse_code() {
|
||||
for rule in Rule::iter() {
|
||||
Linter::parse_code(rule.code())
|
||||
.unwrap_or_else(|| panic!("couldn't parse {:?}", rule.code()));
|
||||
let code = rule.code();
|
||||
let (linter, rest) =
|
||||
Linter::parse_code(code).unwrap_or_else(|| panic!("couldn't parse {:?}", code));
|
||||
assert_eq!(code, format!("{}{rest}", linter.common_prefix()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue