Return DiagnosticGuard from Checker::report_diagnostic (#18232)

Summary
--

This PR adds a `DiagnosticGuard` type to ruff that is adapted from the
`DiagnosticGuard` and `LintDiagnosticGuard` types from ty. This guard is
returned by `Checker::report_diagnostic` and derefs to a
`ruff_diagnostics::Diagnostic` (`OldDiagnostic`), allowing methods like
`OldDiagnostic::set_fix` to be called on the result. On `Drop` the
`DiagnosticGuard` pushes its contained `OldDiagnostic` to the `Checker`.

The main motivation for this is to make a following PR adding a
`SourceFile` to each diagnostic easier. For every rule where a `Checker`
is available, this will now only require modifying
`Checker::report_diagnostic` rather than all the rules.

In the few cases where we need to create a diagnostic before we know if
we actually want to emit it, there is a `DiagnosticGuard::defuse`
method, which consumes the guard without emitting the diagnostic. I was
able to restructure about half of the rules that naively called this to
avoid calling it, but a handful of rules still need it.

One of the fairly common patterns where `defuse` was needed initially
was something like

```rust
let diagnostic = Diagnostic::new(DiagnosticKind, range);

if !checker.enabled(diagnostic.rule()) {
    return;
}
```

So I also added a `Checker::checked_report_diagnostic` method that
handles this check internally. That helped to avoid some additional
`defuse` calls. The name is a bit repetitive, so I'm definitely open to
suggestions there. I included a warning against using it in the docs
since, as we've seen, the conversion from a diagnostic to a rule is
actually pretty expensive.

Test Plan
--

Existing tests
This commit is contained in:
Brent Westbrook 2025-05-28 07:41:31 -04:00 committed by GitHub
parent b60ba75d09
commit a3ee6bb3b5
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
590 changed files with 2983 additions and 3790 deletions

View file

@ -7,10 +7,12 @@ pub(crate) fn violation_metadata(input: DeriveInput) -> syn::Result<TokenStream>
let name = input.ident;
let (impl_generics, ty_generics, where_clause) = &input.generics.split_for_impl();
Ok(quote! {
#[automatically_derived]
#[expect(deprecated)]
impl ruff_diagnostics::ViolationMetadata for #name {
impl #impl_generics ruff_diagnostics::ViolationMetadata for #name #ty_generics #where_clause {
fn rule_name() -> &'static str {
::ruff_macros::kebab_case!(#name)
}