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

@ -12,13 +12,13 @@ use crate::{
use ruff_diagnostics::{Applicability, Edit, Fix};
use ruff_linter::{
Locator,
codes::Rule,
directives::{Flags, extract_directives},
generate_noqa_edits,
linter::check_path,
message::{DiagnosticMessage, Message},
message::Message,
package::PackageRoot,
packaging::detect_package_root,
registry::AsRule,
settings::flags,
source_kind::SourceKind,
};
@ -32,6 +32,7 @@ use ruff_text_size::{Ranged, TextRange};
/// This is serialized on the diagnostic `data` field.
#[derive(Serialize, Deserialize, Debug, Clone)]
pub(crate) struct AssociatedDiagnosticData {
/// The message describing what the fix does, if it exists, or the diagnostic name otherwise.
pub(crate) title: String,
/// Edits to fix the diagnostic. If this is empty, a fix
/// does not exist.
@ -165,15 +166,16 @@ pub(crate) fn check(
messages
.into_iter()
.zip(noqa_edits)
.filter_map(|(message, noqa_edit)| match message {
Message::Diagnostic(diagnostic_message) => Some(to_lsp_diagnostic(
diagnostic_message,
.filter_map(|(message, noqa_edit)| match message.to_rule() {
Some(rule) => Some(to_lsp_diagnostic(
rule,
&message,
noqa_edit,
&source_kind,
locator.to_index(),
encoding,
)),
Message::SyntaxError(_) => {
None => {
if show_syntax_errors {
Some(syntax_error_to_lsp_diagnostic(
&message,
@ -239,28 +241,24 @@ pub(crate) fn fixes_for_diagnostics(
/// Generates an LSP diagnostic with an associated cell index for the diagnostic to go in.
/// If the source kind is a text document, the cell index will always be `0`.
fn to_lsp_diagnostic(
diagnostic: DiagnosticMessage,
rule: Rule,
diagnostic: &Message,
noqa_edit: Option<Edit>,
source_kind: &SourceKind,
index: &LineIndex,
encoding: PositionEncoding,
) -> (usize, lsp_types::Diagnostic) {
let rule = diagnostic.rule();
let DiagnosticMessage {
range: diagnostic_range,
fix,
name,
body,
suggestion,
..
} = diagnostic;
let diagnostic_range = diagnostic.range();
let name = diagnostic.name();
let body = diagnostic.body().to_string();
let fix = diagnostic.fix();
let suggestion = diagnostic.suggestion();
let fix = fix.and_then(|fix| fix.applies(Applicability::Unsafe).then_some(fix));
let data = (fix.is_some() || noqa_edit.is_some())
.then(|| {
let edits = fix
.as_ref()
.into_iter()
.flat_map(Fix::edits)
.map(|edit| lsp_types::TextEdit {
@ -273,7 +271,7 @@ fn to_lsp_diagnostic(
new_text: noqa_edit.into_content().unwrap_or_default().into_string(),
});
serde_json::to_value(AssociatedDiagnosticData {
title: suggestion.unwrap_or_else(|| name.to_string()),
title: suggestion.unwrap_or(name).to_string(),
noqa_edit,
edits,
code: rule.noqa_code().to_string(),