Unify Message variants (#18051)

## Summary

This PR unifies the ruff `Message` enum variants for syntax errors and
rule violations into a single `Message` struct consisting of a shared
`db::Diagnostic` and some additional, optional fields used for some rule
violations.

This version of `Message` is nearly a drop-in replacement for
`ruff_diagnostics::Diagnostic`, which is the next step I have in mind
for the refactor.

I think this is also a useful checkpoint because we could possibly add
some of these optional fields to the new `Diagnostic` type. I think
we've previously discussed wanting support for `Fix`es, but the other
fields seem less relevant, so we may just need to preserve the `Message`
wrapper for a bit longer.

## Test plan

Existing tests

---------

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Brent Westbrook 2025-05-19 13:34:04 -04:00 committed by GitHub
parent 236633cd42
commit d6009eb942
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
27 changed files with 384 additions and 463 deletions

View file

@ -7,6 +7,7 @@ use std::path::Path;
#[cfg(not(fuzzing))]
use anyhow::Result;
use itertools::Itertools;
use ruff_text_size::Ranged;
use rustc_hash::FxHashMap;
use ruff_diagnostics::{Applicability, FixAvailability};
@ -25,7 +26,6 @@ use crate::linter::check_path;
use crate::message::{Emitter, EmitterContext, Message, TextEmitter};
use crate::package::PackageRoot;
use crate::packaging::detect_package_root;
use crate::registry::AsRule;
use crate::settings::types::UnsafeFixes;
use crate::settings::{LinterSettings, flags};
use crate::source_kind::SourceKind;
@ -233,10 +233,9 @@ Source with applied fixes:
let messages = messages
.into_iter()
.filter_map(Message::into_diagnostic_message)
.map(|mut diagnostic| {
let rule = diagnostic.rule();
let fixable = diagnostic.fix.as_ref().is_some_and(|fix| {
.filter_map(|msg| Some((msg.to_rule()?, msg)))
.map(|(rule, mut diagnostic)| {
let fixable = diagnostic.fix().is_some_and(|fix| {
matches!(
fix.applicability(),
Applicability::Safe | Applicability::Unsafe
@ -269,16 +268,22 @@ Either ensure you always emit a fix or change `Violation::FIX_AVAILABILITY` to e
}
assert!(
!(fixable && diagnostic.suggestion.is_none()),
!(fixable && diagnostic.suggestion().is_none()),
"Diagnostic emitted by {rule:?} is fixable but \
`Violation::fix_title` returns `None`"
);
// Not strictly necessary but adds some coverage for this code path
diagnostic.noqa_offset = directives.noqa_line_for.resolve(diagnostic.range.start());
diagnostic.file = source_code.clone();
// Not strictly necessary but adds some coverage for this code path by overriding the
// noqa offset and the source file
let range = diagnostic.range();
diagnostic.noqa_offset = Some(directives.noqa_line_for.resolve(range.start()));
if let Some(annotation) = diagnostic.diagnostic.primary_annotation_mut() {
annotation.set_span(
ruff_db::diagnostic::Span::from(source_code.clone()).with_range(range),
);
}
Message::Diagnostic(diagnostic)
diagnostic
})
.chain(parsed.errors().iter().map(|parse_error| {
Message::from_parse_error(parse_error, &locator, source_code.clone())
@ -311,7 +316,7 @@ fn print_syntax_errors(
/// Print the [`Message::Diagnostic`]s in `messages`.
fn print_diagnostics(mut messages: Vec<Message>, path: &Path, source: &SourceKind) -> String {
messages.retain(Message::is_diagnostic_message);
messages.retain(|msg| !msg.is_syntax_error());
if let Some(notebook) = source.as_ipy_notebook() {
print_jupyter_messages(&messages, path, notebook)