ruff/crates/ruff_macros/src/violation_metadata.rs
Brent Westbrook 9925910a29
Add a ViolationMetadata::rule method (#18234)
Summary
--

This PR adds a macro-generated method to retrieve the `Rule` associated
with a given `Violation` struct, which makes it substantially cheaper
than parsing from the rule name. The rule is then converted to a
`NoqaCode` for storage on the `Message` (and eventually on the new
diagnostic type). The `ViolationMetadata::rule_name` method was now
unused, so the `rule` method replaces it.

Several types had to be moved from the `ruff_diagnostics` crate to the
`ruff_linter` crate to make this work, namely the `Violation` traits and
the old `Diagnostic` type, which had a constructor generic over a
`Violation`.

It's actually a fairly small PR, minus the hundreds of import changes.
The main changes are in these files:

-
[crates/ruff_linter/src/message/mod.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-139754ea310d75f28307008d21c771a190038bd106efe3b9267cc2d6c0fa0921)
-
[crates/ruff_diagnostics/src/lib.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-8e8ea5c586935bf21ea439f24253fcfd5955d2cb130f5377c2fa7bfee3ea3a81)
-
[crates/ruff_linter/src/diagnostic.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-1d0c9aad90d8f9446079c5be5f284150d97797158715bd9729e6f1f70246297a)
-
[crates/ruff_linter/src/lib.rs](https://github.com/astral-sh/ruff/pull/18234/files#diff-eb93ef7e78a612f5fa9145412c75cf6b1a5cefba1c2233e4a11a880a1ce1fbcc)

Test Plan
--

Existing tests
2025-05-28 09:27:09 -04:00

68 lines
1.9 KiB
Rust

use proc_macro2::TokenStream;
use quote::quote;
use syn::{Attribute, DeriveInput, Error, Lit, LitStr, Meta};
pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result<TokenStream> {
let docs = get_docs(&input.attrs)?;
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
#[expect(deprecated)]
impl #impl_generics crate::ViolationMetadata for #name #ty_generics #where_clause {
fn rule() -> crate::registry::Rule {
crate::registry::Rule::#name
}
fn explain() -> Option<&'static str> {
Some(#docs)
}
}
})
}
/// Collect all doc comment attributes into a string
fn get_docs(attrs: &[Attribute]) -> syn::Result<String> {
let mut explanation = String::new();
for attr in attrs {
if attr.path().is_ident("doc") {
if let Some(lit) = parse_attr(["doc"], attr) {
let value = lit.value();
// `/// ` adds
let line = value.strip_prefix(' ').unwrap_or(&value);
explanation.push_str(line);
explanation.push('\n');
} else {
return Err(Error::new_spanned(attr, "unimplemented doc comment style"));
}
}
}
Ok(explanation)
}
fn parse_attr<'a, const LEN: usize>(
path: [&'static str; LEN],
attr: &'a Attribute,
) -> Option<&'a LitStr> {
if let Meta::NameValue(name_value) = &attr.meta {
let path_idents = name_value
.path
.segments
.iter()
.map(|segment| &segment.ident);
if itertools::equal(path_idents, path) {
if let syn::Expr::Lit(syn::ExprLit {
lit: Lit::Str(lit), ..
}) = &name_value.value
{
return Some(lit);
}
}
}
None
}