mirror of
https://github.com/astral-sh/ruff.git
synced 2025-08-04 02:39:12 +00:00
[ty] Move diagnostics API for the server (#18308)
## Summary This PR moves the diagnostics API for the language server out from the request handler module to the diagnostics API module. This is in preparation to add support for publishing diagnostics.
This commit is contained in:
parent
14c3755445
commit
d95b029862
2 changed files with 167 additions and 153 deletions
|
@ -1,6 +1,18 @@
|
|||
use lsp_server::ErrorCode;
|
||||
use lsp_types::{PublishDiagnosticsParams, Url, notification::PublishDiagnostics};
|
||||
use lsp_types::notification::PublishDiagnostics;
|
||||
use lsp_types::{
|
||||
Diagnostic, DiagnosticRelatedInformation, DiagnosticSeverity, DiagnosticTag, NumberOrString,
|
||||
PublishDiagnosticsParams, Range, Url,
|
||||
};
|
||||
|
||||
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
use ty_project::{Db, ProjectDatabase};
|
||||
|
||||
use crate::DocumentSnapshot;
|
||||
use crate::PositionEncoding;
|
||||
use crate::document::{FileRangeExt, ToRangeExt};
|
||||
use crate::server::Result;
|
||||
use crate::server::client::Notifier;
|
||||
|
||||
|
@ -16,3 +28,152 @@ pub(super) fn clear_diagnostics(uri: &Url, notifier: &Notifier) -> Result<()> {
|
|||
.with_failure_code(ErrorCode::InternalError)?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(super) fn compute_diagnostics(
|
||||
db: &ProjectDatabase,
|
||||
snapshot: &DocumentSnapshot,
|
||||
) -> Vec<Diagnostic> {
|
||||
let Some(file) = snapshot.file(db) else {
|
||||
tracing::info!(
|
||||
"No file found for snapshot for `{}`",
|
||||
snapshot.query().file_url()
|
||||
);
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let diagnostics = match db.check_file(file) {
|
||||
Ok(diagnostics) => diagnostics,
|
||||
Err(cancelled) => {
|
||||
tracing::info!("Diagnostics computation {cancelled}");
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
diagnostics
|
||||
.as_slice()
|
||||
.iter()
|
||||
.map(|message| to_lsp_diagnostic(db, message, snapshot.encoding()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
/// Converts the tool specific [`Diagnostic`][ruff_db::diagnostic::Diagnostic] to an LSP
|
||||
/// [`Diagnostic`].
|
||||
fn to_lsp_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &ruff_db::diagnostic::Diagnostic,
|
||||
encoding: PositionEncoding,
|
||||
) -> Diagnostic {
|
||||
let range = if let Some(span) = diagnostic.primary_span() {
|
||||
let file = span.expect_ty_file();
|
||||
let index = line_index(db.upcast(), file);
|
||||
let source = source_text(db.upcast(), file);
|
||||
|
||||
span.range()
|
||||
.map(|range| range.to_lsp_range(&source, &index, encoding))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Range::default()
|
||||
};
|
||||
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => DiagnosticSeverity::INFORMATION,
|
||||
Severity::Warning => DiagnosticSeverity::WARNING,
|
||||
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());
|
||||
|
||||
let code_description = diagnostic
|
||||
.id()
|
||||
.is_lint()
|
||||
.then(|| {
|
||||
Some(lsp_types::CodeDescription {
|
||||
href: lsp_types::Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id()))
|
||||
.ok()?,
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
|
||||
let mut related_information = Vec::new();
|
||||
|
||||
related_information.extend(
|
||||
diagnostic
|
||||
.secondary_annotations()
|
||||
.filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)),
|
||||
);
|
||||
|
||||
for sub_diagnostic in diagnostic.sub_diagnostics() {
|
||||
related_information.extend(sub_diagnostic_to_related_information(
|
||||
db,
|
||||
sub_diagnostic,
|
||||
encoding,
|
||||
));
|
||||
|
||||
related_information.extend(
|
||||
sub_diagnostic
|
||||
.annotations()
|
||||
.iter()
|
||||
.filter_map(|annotation| {
|
||||
annotation_to_related_information(db, annotation, encoding)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Diagnostic {
|
||||
range,
|
||||
severity: Some(severity),
|
||||
tags,
|
||||
code: Some(NumberOrString::String(diagnostic.id().to_string())),
|
||||
code_description,
|
||||
source: Some("ty".into()),
|
||||
message: diagnostic.concise_message().to_string(),
|
||||
related_information: Some(related_information),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
/// Converts an [`Annotation`] to a [`DiagnosticRelatedInformation`].
|
||||
fn annotation_to_related_information(
|
||||
db: &dyn Db,
|
||||
annotation: &Annotation,
|
||||
encoding: PositionEncoding,
|
||||
) -> Option<DiagnosticRelatedInformation> {
|
||||
let span = annotation.get_span();
|
||||
|
||||
let annotation_message = annotation.get_message()?;
|
||||
let range = FileRange::try_from(span).ok()?;
|
||||
let location = range.to_location(db.upcast(), encoding)?;
|
||||
|
||||
Some(DiagnosticRelatedInformation {
|
||||
location,
|
||||
message: annotation_message.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
/// Converts a [`SubDiagnostic`] to a [`DiagnosticRelatedInformation`].
|
||||
fn sub_diagnostic_to_related_information(
|
||||
db: &dyn Db,
|
||||
diagnostic: &SubDiagnostic,
|
||||
encoding: PositionEncoding,
|
||||
) -> Option<DiagnosticRelatedInformation> {
|
||||
let primary_annotation = diagnostic.primary_annotation()?;
|
||||
|
||||
let span = primary_annotation.get_span();
|
||||
let range = FileRange::try_from(span).ok()?;
|
||||
let location = range.to_location(db.upcast(), encoding)?;
|
||||
|
||||
Some(DiagnosticRelatedInformation {
|
||||
location,
|
||||
message: diagnostic.concise_message().to_string(),
|
||||
})
|
||||
}
|
||||
|
|
|
@ -2,20 +2,15 @@ use std::borrow::Cow;
|
|||
|
||||
use lsp_types::request::DocumentDiagnosticRequest;
|
||||
use lsp_types::{
|
||||
Diagnostic, DiagnosticSeverity, DiagnosticTag, DocumentDiagnosticParams,
|
||||
DocumentDiagnosticReport, DocumentDiagnosticReportResult, FullDocumentDiagnosticReport,
|
||||
NumberOrString, Range, RelatedFullDocumentDiagnosticReport,
|
||||
DocumentDiagnosticParams, DocumentDiagnosticReport, DocumentDiagnosticReportResult,
|
||||
FullDocumentDiagnosticReport, RelatedFullDocumentDiagnosticReport,
|
||||
};
|
||||
|
||||
use crate::PositionEncoding;
|
||||
use crate::document::{FileRangeExt, ToRangeExt};
|
||||
use crate::server::api::diagnostics::compute_diagnostics;
|
||||
use crate::server::api::traits::{BackgroundDocumentRequestHandler, RequestHandler};
|
||||
use crate::server::{Result, client::Notifier};
|
||||
use crate::session::DocumentSnapshot;
|
||||
use ruff_db::diagnostic::{Annotation, Severity, SubDiagnostic};
|
||||
use ruff_db::files::FileRange;
|
||||
use ruff_db::source::{line_index, source_text};
|
||||
use ty_project::{Db, ProjectDatabase};
|
||||
use ty_project::ProjectDatabase;
|
||||
|
||||
pub(crate) struct DocumentDiagnosticRequestHandler;
|
||||
|
||||
|
@ -34,7 +29,7 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
|
|||
_notifier: Notifier,
|
||||
_params: DocumentDiagnosticParams,
|
||||
) -> Result<DocumentDiagnosticReportResult> {
|
||||
let diagnostics = compute_diagnostics(&snapshot, db);
|
||||
let diagnostics = compute_diagnostics(db, &snapshot);
|
||||
|
||||
Ok(DocumentDiagnosticReportResult::Report(
|
||||
DocumentDiagnosticReport::Full(RelatedFullDocumentDiagnosticReport {
|
||||
|
@ -47,145 +42,3 @@ impl BackgroundDocumentRequestHandler for DocumentDiagnosticRequestHandler {
|
|||
))
|
||||
}
|
||||
}
|
||||
|
||||
fn compute_diagnostics(snapshot: &DocumentSnapshot, db: &ProjectDatabase) -> Vec<Diagnostic> {
|
||||
let Some(file) = snapshot.file(db) else {
|
||||
tracing::info!(
|
||||
"No file found for snapshot for `{}`",
|
||||
snapshot.query().file_url()
|
||||
);
|
||||
return vec![];
|
||||
};
|
||||
|
||||
let diagnostics = match db.check_file(file) {
|
||||
Ok(diagnostics) => diagnostics,
|
||||
Err(cancelled) => {
|
||||
tracing::info!("Diagnostics computation {cancelled}");
|
||||
return vec![];
|
||||
}
|
||||
};
|
||||
|
||||
diagnostics
|
||||
.as_slice()
|
||||
.iter()
|
||||
.map(|message| to_lsp_diagnostic(db, message, snapshot.encoding()))
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn to_lsp_diagnostic(
|
||||
db: &dyn Db,
|
||||
diagnostic: &ruff_db::diagnostic::Diagnostic,
|
||||
encoding: crate::PositionEncoding,
|
||||
) -> Diagnostic {
|
||||
let range = if let Some(span) = diagnostic.primary_span() {
|
||||
let file = span.expect_ty_file();
|
||||
let index = line_index(db.upcast(), file);
|
||||
let source = source_text(db.upcast(), file);
|
||||
|
||||
span.range()
|
||||
.map(|range| range.to_lsp_range(&source, &index, encoding))
|
||||
.unwrap_or_default()
|
||||
} else {
|
||||
Range::default()
|
||||
};
|
||||
|
||||
let severity = match diagnostic.severity() {
|
||||
Severity::Info => DiagnosticSeverity::INFORMATION,
|
||||
Severity::Warning => DiagnosticSeverity::WARNING,
|
||||
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());
|
||||
|
||||
let code_description = diagnostic
|
||||
.id()
|
||||
.is_lint()
|
||||
.then(|| {
|
||||
Some(lsp_types::CodeDescription {
|
||||
href: lsp_types::Url::parse(&format!("https://ty.dev/rules#{}", diagnostic.id()))
|
||||
.ok()?,
|
||||
})
|
||||
})
|
||||
.flatten();
|
||||
|
||||
let mut related_information = Vec::new();
|
||||
|
||||
related_information.extend(
|
||||
diagnostic
|
||||
.secondary_annotations()
|
||||
.filter_map(|annotation| annotation_to_related_information(db, annotation, encoding)),
|
||||
);
|
||||
|
||||
for sub_diagnostic in diagnostic.sub_diagnostics() {
|
||||
related_information.extend(sub_diagnostic_to_related_information(
|
||||
db,
|
||||
sub_diagnostic,
|
||||
encoding,
|
||||
));
|
||||
|
||||
related_information.extend(
|
||||
sub_diagnostic
|
||||
.annotations()
|
||||
.iter()
|
||||
.filter_map(|annotation| {
|
||||
annotation_to_related_information(db, annotation, encoding)
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
Diagnostic {
|
||||
range,
|
||||
severity: Some(severity),
|
||||
tags,
|
||||
code: Some(NumberOrString::String(diagnostic.id().to_string())),
|
||||
code_description,
|
||||
source: Some("ty".into()),
|
||||
message: diagnostic.concise_message().to_string(),
|
||||
related_information: Some(related_information),
|
||||
data: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn annotation_to_related_information(
|
||||
db: &dyn Db,
|
||||
annotation: &Annotation,
|
||||
encoding: PositionEncoding,
|
||||
) -> Option<lsp_types::DiagnosticRelatedInformation> {
|
||||
let span = annotation.get_span();
|
||||
|
||||
let annotation_message = annotation.get_message()?;
|
||||
let range = FileRange::try_from(span).ok()?;
|
||||
let location = range.to_location(db.upcast(), encoding)?;
|
||||
|
||||
Some(lsp_types::DiagnosticRelatedInformation {
|
||||
location,
|
||||
message: annotation_message.to_string(),
|
||||
})
|
||||
}
|
||||
|
||||
fn sub_diagnostic_to_related_information(
|
||||
db: &dyn Db,
|
||||
diagnostic: &SubDiagnostic,
|
||||
encoding: PositionEncoding,
|
||||
) -> Option<lsp_types::DiagnosticRelatedInformation> {
|
||||
let primary_annotation = diagnostic.primary_annotation()?;
|
||||
|
||||
let span = primary_annotation.get_span();
|
||||
let range = FileRange::try_from(span).ok()?;
|
||||
let location = range.to_location(db.upcast(), encoding)?;
|
||||
|
||||
Some(lsp_types::DiagnosticRelatedInformation {
|
||||
location,
|
||||
message: diagnostic.concise_message().to_string(),
|
||||
})
|
||||
}
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue