Inline DiagnosticKind into other diagnostic types (#18074)

## Summary

This PR deletes the `DiagnosticKind` type by inlining its three fields
(`name`, `body`, and `suggestion`) into three other diagnostic types:
`Diagnostic`, `DiagnosticMessage`, and `CacheMessage`.

Instead of deferring to an internal `DiagnosticKind`, both `Diagnostic`
and `DiagnosticMessage` now have their own macro-generated `AsRule`
implementations.

This should make both https://github.com/astral-sh/ruff/pull/18051 and
another follow-up PR changing the type of `name` on `CacheMessage`
easier since its type will be able to change separately from
`Diagnostic` and `DiagnosticMessage`.

## Test Plan

Existing tests
This commit is contained in:
Brent Westbrook 2025-05-15 10:27:21 -04:00 committed by GitHub
parent b35bf8ae07
commit e2c5b83fe1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
41 changed files with 604 additions and 621 deletions

View file

@ -1,19 +1,10 @@
use heck::ToKebabCase;
use proc_macro2::TokenStream;
pub(crate) fn kebab_case(input: &syn::Ident) -> TokenStream {
let screaming_snake_case = input.to_string();
let s = input.to_string();
let mut kebab_case = String::with_capacity(screaming_snake_case.len());
for (i, word) in screaming_snake_case.split('_').enumerate() {
if i > 0 {
kebab_case.push('-');
}
kebab_case.push_str(&word.to_lowercase());
}
let kebab_case_lit = syn::LitStr::new(&kebab_case, input.span());
let kebab_case_lit = syn::LitStr::new(&s.to_kebab_case(), input.span());
quote::quote!(#kebab_case_lit)
}

View file

@ -49,7 +49,7 @@ pub fn derive_combine(input: TokenStream) -> TokenStream {
.into()
}
/// Converts a screaming snake case identifier to a kebab case string.
/// Converts an identifier to a kebab case string.
#[proc_macro]
pub fn kebab_case(input: TokenStream) -> TokenStream {
let input = parse_macro_input!(input as syn::Ident);

View file

@ -404,8 +404,6 @@ fn register_rules<'a>(input: impl Iterator<Item = &'a Rule>) -> TokenStream {
let mut rule_fixable_match_arms = quote!();
let mut rule_explanation_match_arms = quote!();
let mut from_impls_for_diagnostic_kind = quote!();
for Rule {
name, attrs, path, ..
} in input
@ -421,10 +419,6 @@ fn register_rules<'a>(input: impl Iterator<Item = &'a Rule>) -> TokenStream {
quote! {#(#attrs)* Self::#name => <#path as ruff_diagnostics::Violation>::FIX_AVAILABILITY,},
);
rule_explanation_match_arms.extend(quote! {#(#attrs)* Self::#name => #path::explain(),});
// Enable conversion from `DiagnosticKind` to `Rule`.
from_impls_for_diagnostic_kind
.extend(quote! {#(#attrs)* stringify!(#name) => Rule::#name,});
}
quote! {
@ -443,6 +437,9 @@ fn register_rules<'a>(input: impl Iterator<Item = &'a Rule>) -> TokenStream {
::ruff_macros::CacheKey,
AsRefStr,
::strum_macros::IntoStaticStr,
::strum_macros::EnumString,
::serde::Serialize,
::serde::Deserialize,
)]
#[repr(u16)]
#[strum(serialize_all = "kebab-case")]
@ -466,13 +463,19 @@ fn register_rules<'a>(input: impl Iterator<Item = &'a Rule>) -> TokenStream {
}
}
impl AsRule for ruff_diagnostics::DiagnosticKind {
impl AsRule for ruff_diagnostics::Diagnostic {
fn rule(&self) -> Rule {
match self.name.as_str() {
#from_impls_for_diagnostic_kind
_ => unreachable!("invalid rule name: {}", self.name),
}
self.name
.parse()
.unwrap_or_else(|_| unreachable!("invalid rule name: {}", self.name))
}
}
impl AsRule for crate::message::DiagnosticMessage {
fn rule(&self) -> Rule {
self.name
.parse()
.unwrap_or_else(|_| unreachable!("invalid rule name: {}", self.name))
}
}
}

View file

@ -12,7 +12,7 @@ pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result<TokenStream>
#[expect(deprecated)]
impl ruff_diagnostics::ViolationMetadata for #name {
fn rule_name() -> &'static str {
stringify!(#name)
::ruff_macros::kebab_case!(#name)
}
fn explain() -> Option<&'static str> {