[red-knot] Add support for the LSP diagnostic tag (#17657)

Co-authored-by: Micha Reiser <micha@reiser.io>
This commit is contained in:
Eric Botti 2025-05-03 14:35:03 -04:00 committed by GitHub
parent b51c4f82ea
commit 8535af8516
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 71 additions and 4 deletions

View file

@ -227,6 +227,11 @@ impl Diagnostic {
pub fn primary_span(&self) -> Option<Span> {
self.primary_annotation().map(|ann| ann.span.clone())
}
/// Returns the tags from the primary annotation of this diagnostic if it exists.
pub fn primary_tags(&self) -> Option<&[DiagnosticTag]> {
self.primary_annotation().map(|ann| ann.tags.as_slice())
}
}
#[derive(Debug, Clone, Eq, PartialEq)]
@ -338,6 +343,8 @@ pub struct Annotation {
/// Whether this annotation is "primary" or not. When it isn't primary, an
/// annotation is said to be "secondary."
is_primary: bool,
/// The diagnostic tags associated with this annotation.
tags: Vec<DiagnosticTag>,
}
impl Annotation {
@ -355,6 +362,7 @@ impl Annotation {
span,
message: None,
is_primary: true,
tags: Vec::new(),
}
}
@ -370,6 +378,7 @@ impl Annotation {
span,
message: None,
is_primary: false,
tags: Vec::new(),
}
}
@ -412,6 +421,36 @@ impl Annotation {
pub fn get_span(&self) -> &Span {
&self.span
}
/// Returns the tags associated with this annotation.
pub fn get_tags(&self) -> &[DiagnosticTag] {
&self.tags
}
/// Attaches this tag to this annotation.
///
/// It will not replace any existing tags.
pub fn tag(mut self, tag: DiagnosticTag) -> Annotation {
self.tags.push(tag);
self
}
/// Attaches an additional tag to this annotation.
pub fn push_tag(&mut self, tag: DiagnosticTag) {
self.tags.push(tag);
}
}
/// Tags that can be associated with an annotation.
///
/// These tags are used to provide additional information about the annotation.
/// and are passed through to the language server protocol.
#[derive(Debug, Clone, Eq, PartialEq)]
pub enum DiagnosticTag {
/// Unused or unnecessary code. Used for unused parameters, unreachable code, etc.
Unnecessary,
/// Deprecated or obsolete code.
Deprecated,
}
/// A string identifier for a lint rule.

View file

@ -1,6 +1,7 @@
use std::fmt;
use drop_bomb::DebugDropBomb;
use ruff_db::diagnostic::DiagnosticTag;
use ruff_db::{
diagnostic::{Annotation, Diagnostic, DiagnosticId, IntoDiagnosticMessage, Severity, Span},
files::File,
@ -259,6 +260,21 @@ impl LintDiagnosticGuard<'_, '_> {
let ann = self.primary_annotation_mut().unwrap();
ann.set_message(message);
}
/// Adds a tag on the primary annotation for this diagnostic.
///
/// This tag is associated with the primary annotation created
/// for every `Diagnostic` that uses the `LintDiagnosticGuard` API.
/// Specifically, the annotation is derived from the `TextRange` given to
/// the `InferContext::report_lint` API.
///
/// Callers can add additional primary or secondary annotations via the
/// `DerefMut` trait implementation to a `Diagnostic`.
#[expect(dead_code)]
pub(super) fn add_primary_tag(&mut self, tag: DiagnosticTag) {
let ann = self.primary_annotation_mut().unwrap();
ann.push_tag(tag);
}
}
impl std::ops::Deref for LintDiagnosticGuard<'_, '_> {

View file

@ -2,9 +2,9 @@ use std::borrow::Cow;
use lsp_types::request::DocumentDiagnosticRequest;
use lsp_types::{
Diagnostic, DiagnosticSeverity, DocumentDiagnosticParams, DocumentDiagnosticReport,
DocumentDiagnosticReportResult, FullDocumentDiagnosticReport, NumberOrString, Range,
RelatedFullDocumentDiagnosticReport, Url,
Diagnostic, DiagnosticSeverity, DiagnosticTag, DocumentDiagnosticParams,
DocumentDiagnosticReport, DocumentDiagnosticReportResult, FullDocumentDiagnosticReport,
NumberOrString, Range, RelatedFullDocumentDiagnosticReport, Url,
};
use crate::document::ToRangeExt;
@ -92,10 +92,22 @@ fn to_lsp_diagnostic(
Severity::Error | Severity::Fatal => DiagnosticSeverity::ERROR,
};
let tags = diagnostic
.primary_tags()
.map(|tags| {
tags.iter()
.map(|tag| match tag {
ruff_db::diagnostic::DiagnosticTag::Unnecessary => DiagnosticTag::UNNECESSARY,
ruff_db::diagnostic::DiagnosticTag::Deprecated => DiagnosticTag::DEPRECATED,
})
.collect::<Vec<DiagnosticTag>>()
})
.filter(|mapped_tags| !mapped_tags.is_empty());
Diagnostic {
range,
severity: Some(severity),
tags: None,
tags,
code: Some(NumberOrString::String(diagnostic.id().to_string())),
code_description: None,
source: Some("ty".into()),