Add internal hidden rules for testing (#9747)

Updated implementation of https://github.com/astral-sh/ruff/pull/7369
which was left out in the cold.

This was motivated again following changes in #9691 and #9689 where we
could not test the changes without actually deprecating or removing
rules.

---

Follow-up to discussion in https://github.com/astral-sh/ruff/pull/7210

Moves integration tests from using rules that are transitively in
nursery / preview groups to dedicated test rules that only exist during
development. These rules always raise violations (they do not require
specific file behavior). The rules are not available in production or in
the documentation.

Uses features instead of `cfg(test)` for cross-crate support per
https://github.com/rust-lang/cargo/issues/8379
This commit is contained in:
Zanie Blue 2024-02-01 08:44:51 -06:00 committed by GitHub
parent 2cc8acb0b7
commit f18e7d40ac
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
13 changed files with 541 additions and 196 deletions

View file

@ -8,7 +8,7 @@ use syn::{
Ident, ItemFn, LitStr, Pat, Path, Stmt, Token,
};
use crate::rule_code_prefix::{get_prefix_ident, if_all_same};
use crate::rule_code_prefix::{get_prefix_ident, intersection_all};
/// A rule entry in the big match statement such a
/// `(Pycodestyle, "E112") => (RuleGroup::Nursery, rules::pycodestyle::rules::logical_lines::NoIndentedBlock),`
@ -142,12 +142,13 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
for (prefix, rules) in &rules_by_prefix {
let prefix_ident = get_prefix_ident(prefix);
let attr = match if_all_same(rules.iter().map(|(.., attrs)| attrs)) {
Some(attr) => quote!(#(#attr)*),
None => quote!(),
let attrs = intersection_all(rules.iter().map(|(.., attrs)| attrs.as_slice()));
let attrs = match attrs.as_slice() {
[] => quote!(),
[..] => quote!(#(#attrs)*),
};
all_codes.push(quote! {
#attr Self::#linter(#linter::#prefix_ident)
#attrs Self::#linter(#linter::#prefix_ident)
});
}
@ -159,12 +160,13 @@ pub(crate) fn map_codes(func: &ItemFn) -> syn::Result<TokenStream> {
quote!(#(#attrs)* Rule::#rule_name)
});
let prefix_ident = get_prefix_ident(&prefix);
let attr = match if_all_same(rules.iter().map(|(.., attrs)| attrs)) {
Some(attr) => quote!(#(#attr)*),
None => quote!(),
let attrs = intersection_all(rules.iter().map(|(.., attrs)| attrs.as_slice()));
let attrs = match attrs.as_slice() {
[] => quote!(),
[..] => quote!(#(#attrs)*),
};
prefix_into_iter_match_arms.extend(quote! {
#attr #linter::#prefix_ident => vec![#(#rule_paths,)*].into_iter(),
#attrs #linter::#prefix_ident => vec![#(#rule_paths,)*].into_iter(),
});
}

View file

@ -89,21 +89,30 @@ fn attributes_for_prefix(
codes: &BTreeSet<String>,
attributes: &BTreeMap<String, &[Attribute]>,
) -> proc_macro2::TokenStream {
match if_all_same(codes.iter().map(|code| attributes[code])) {
Some(attr) => quote!(#(#attr)*),
None => quote!(),
let attrs = intersection_all(codes.iter().map(|code| attributes[code]));
match attrs.as_slice() {
[] => quote!(),
[..] => quote!(#(#attrs)*),
}
}
/// If all values in an iterator are the same, return that value. Otherwise,
/// return `None`.
pub(crate) fn if_all_same<T: PartialEq>(iter: impl Iterator<Item = T>) -> Option<T> {
let mut iter = iter.peekable();
let first = iter.next()?;
if iter.all(|x| x == first) {
Some(first)
/// Collect all the items from an iterable of slices that are present in all slices.
pub(crate) fn intersection_all<'a, T: PartialEq>(
mut slices: impl Iterator<Item = &'a [T]>,
) -> Vec<&'a T> {
if let Some(slice) = slices.next() {
// Collect all the items in the first slice
let mut intersection = Vec::with_capacity(slice.len());
for item in slice {
intersection.push(item);
}
// Then only keep items that are present in each of the remaining slices
for slice in slices {
intersection.retain(|item| slice.contains(item));
}
intersection
} else {
None
Vec::new()
}
}